Skip to content

Commit 1925a54

Browse files
committed
fix: avoid race condition in singleton instance method
1 parent 8def74b commit 1925a54

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

instrumentation/base/lib/opentelemetry/instrumentation/base.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ class << self
6969
integer: ->(v) { v.is_a?(Integer) },
7070
string: ->(v) { v.is_a?(String) }
7171
}.freeze
72+
SINGLETON_MUTEX = Thread::Mutex.new
7273

73-
private_constant :NAME_REGEX, :VALIDATORS
74+
private_constant :NAME_REGEX, :VALIDATORS, :SINGLETON_MUTEX
7475

7576
private :new
7677

@@ -163,8 +164,10 @@ def option(name, default:, validate:)
163164
end
164165

165166
def instance
166-
@instance ||= new(instrumentation_name, instrumentation_version, install_blk,
167-
present_blk, compatible_blk, options)
167+
@instance || SINGLETON_MUTEX.synchronize do
168+
@instance ||= new(instrumentation_name, instrumentation_version, install_blk,
169+
present_blk, compatible_blk, options)
170+
end
168171
end
169172

170173
private

instrumentation/base/test/instrumentation/base_test.rb

+24
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,33 @@ def initialize(*args)
5959
end
6060

6161
describe '.instance' do
62+
let(:instrumentation) do
63+
Class.new(OpenTelemetry::Instrumentation::Base) do
64+
instrumentation_name 'test_instrumentation'
65+
instrumentation_version '0.1.1'
66+
67+
def initialize(*args)
68+
# Simulate latency by hinting the VM should switch tasks
69+
# (this can also be accomplished by something like `sleep(0.1)`).
70+
# This replicates the worst-case scenario when using default assignment
71+
# to obtain a singleton, i.e. that the scheduler switches threads between
72+
# the nil check and object initialization.
73+
Thread.pass
74+
super
75+
end
76+
end
77+
end
78+
6279
it 'returns an instance' do
6380
_(instrumentation.instance).must_be_instance_of(instrumentation)
6481
end
82+
83+
it 'returns the same singleton instance to every thread' do
84+
object_ids = Array.new(2).map { Thread.new { instrumentation.instance } }
85+
.map { |thr| thr.join.value }
86+
87+
_(object_ids.uniq.count).must_equal(1)
88+
end
6589
end
6690

6791
describe '.option' do

0 commit comments

Comments
 (0)