-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add Core::Utils::OnlyOnceSuccessful to execute code with only one suc…
…cess
- Loading branch information
1 parent
e8163d8
commit c33435c
Showing
4 changed files
with
140 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'only_once' | ||
|
||
module Datadog | ||
module Core | ||
module Utils | ||
# Helper class to execute something with only one success. | ||
# | ||
# This is useful for cases where we want to ensure that a block of code is only executed once, and only if it | ||
# succeeds. One such example is sending app-started telemetry event. | ||
# | ||
# Successful execution is determined by the return value of the block: any truthy value is considered success. | ||
# | ||
# Thread-safe when used correctly (e.g. be careful of races when lazily initializing instances of this class). | ||
# | ||
# Note: In its current state, this class is not Ractor-safe. | ||
# In https://github.com/DataDog/dd-trace-rb/pull/1398#issuecomment-797378810 we have a discussion of alternatives, | ||
# including an alternative implementation that is Ractor-safe once spent. | ||
class OnlyOnceSuccessful < OnlyOnce | ||
def run | ||
@mutex.synchronize do | ||
return if @ran_once | ||
|
||
result = yield | ||
@ran_once = !!result | ||
|
||
result | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module Datadog | ||
module Core | ||
module Utils | ||
class OnlyOnceSuccessful < Datadog::Core::Utils::OnlyOnce | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
require 'datadog/core/utils/only_once_successful' | ||
|
||
RSpec.describe Datadog::Core::Utils::OnlyOnceSuccessful do | ||
subject(:only_once_successful) { described_class.new } | ||
|
||
describe '#run' do | ||
context 'before running once' do | ||
it do | ||
expect { |block| only_once_successful.run(&block) }.to yield_control | ||
end | ||
|
||
it 'returns the result of the block ran' do | ||
expect(only_once_successful.run { :result }).to be :result | ||
end | ||
end | ||
|
||
context 'after running once' do | ||
let(:result) { nil } | ||
|
||
before do | ||
only_once_successful.run { result } | ||
end | ||
|
||
context 'when block returns truthy value' do | ||
let(:result) { true } | ||
|
||
it do | ||
expect { |block| only_once_successful.run(&block) }.to_not yield_control | ||
end | ||
|
||
it do | ||
expect(only_once_successful.run { :result }).to be nil | ||
end | ||
end | ||
|
||
context 'when block returns falsey value' do | ||
let(:result) { false } | ||
|
||
it do | ||
expect { |block| only_once_successful.run(&block) }.to yield_control | ||
end | ||
|
||
it 'runs again until block returns truthy value' do | ||
expect(only_once_successful.run { :result }).to be :result | ||
|
||
expect(only_once_successful.run { :result }).to be nil | ||
end | ||
end | ||
end | ||
|
||
context 'when run throws an exception' do | ||
it 'propagates the exception out' do | ||
exception = RuntimeError.new('boom') | ||
|
||
expect { only_once_successful.run { raise exception } }.to raise_exception(exception) | ||
end | ||
|
||
it 'runs again' do | ||
only_once_successful.run { raise 'boom' } rescue nil | ||
|
||
expect { |block| only_once_successful.run(&block) }.to yield_control | ||
end | ||
end | ||
end | ||
|
||
describe '#ran?' do | ||
context 'before running once' do | ||
it do | ||
expect(only_once_successful.ran?).to be false | ||
end | ||
end | ||
|
||
context 'after running once' do | ||
let(:result) { nil } | ||
|
||
before do | ||
only_once_successful.run { result } | ||
end | ||
|
||
context 'when block returns truthy value' do | ||
let(:result) { true } | ||
|
||
it do | ||
expect(only_once_successful.ran?).to be true | ||
end | ||
end | ||
|
||
context 'when block returns falsey value' do | ||
it do | ||
expect(only_once_successful.ran?).to be false | ||
end | ||
end | ||
end | ||
end | ||
end |