Skip to content

Commit

Permalink
add Core::Utils::OnlyOnceSuccessful to execute code with only one suc…
Browse files Browse the repository at this point in the history
…cess
  • Loading branch information
anmarchenko committed Jun 17, 2024
1 parent e8163d8 commit c33435c
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 0 deletions.
34 changes: 34 additions & 0 deletions lib/datadog/core/utils/only_once_successful.rb
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
3 changes: 3 additions & 0 deletions sig/datadog/core/utils/only_once.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module Datadog
module Core
module Utils
class OnlyOnce
@ran_once: bool
@mutex: Thread::Mutex

def initialize: () -> untyped

def run: () { () -> untyped } -> untyped
Expand Down
8 changes: 8 additions & 0 deletions sig/datadog/core/utils/only_once_successful.rbs
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
95 changes: 95 additions & 0 deletions spec/datadog/core/utils/only_once_successful_spec.rb
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

0 comments on commit c33435c

Please sign in to comment.