Skip to content

Commit 025268b

Browse files
committed
Allow ThreadLocalVar to be initialized with a block
ThreadLocalVars can now receive a block in the #initialize method, which will be called to initialize the value on each thread. This allows default initialization to work with non-value types, where each thread gets its own copy: v = ThreadLocalVar.new { {} } h1 = Thread.new { v.value }.value h2 = Thread.new { v.value }.value h1.equal? h2 # -> false
1 parent 9eae762 commit 025268b

File tree

5 files changed

+75
-6
lines changed

5 files changed

+75
-6
lines changed

lib/concurrent/atomic/abstract_thread_local_var.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,17 @@ module Concurrent
88
class AbstractThreadLocalVar
99

1010
# @!macro thread_local_var_method_initialize
11-
def initialize(default = nil)
12-
@default = default
11+
def initialize(default = nil, &default_block)
12+
if default && block_given?
13+
raise ArgumentError, "Cannot use both value and block as default value"
14+
end
15+
16+
if block_given?
17+
@default_block = default_block
18+
else
19+
@default = default
20+
end
21+
1322
allocate_storage
1423
end
1524

@@ -42,5 +51,14 @@ def bind(value, &block)
4251
def allocate_storage
4352
raise NotImplementedError
4453
end
54+
55+
# @!visibility private
56+
def default
57+
if @default_block
58+
self.value = @default_block.call
59+
else
60+
@default
61+
end
62+
end
4563
end
4664
end

lib/concurrent/atomic/java_thread_local_var.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def value
1313
value = @var.get
1414

1515
if value.nil?
16-
@default
16+
default
1717
elsif value == NULL
1818
nil
1919
else

lib/concurrent/atomic/ruby_thread_local_var.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ def value
4040
if array = get_threadlocal_array
4141
value = array[@index]
4242
if value.nil?
43-
@default
43+
default
4444
elsif value.equal?(NULL)
4545
nil
4646
else
4747
value
4848
end
4949
else
50-
@default
50+
default
5151
end
5252
end
5353

@@ -135,12 +135,20 @@ def value_for(thread)
135135
if array = get_threadlocal_array(thread)
136136
value = array[@index]
137137
if value.nil?
138-
@default
138+
default_for(thread)
139139
elsif value.equal?(NULL)
140140
nil
141141
else
142142
value
143143
end
144+
else
145+
default_for(thread)
146+
end
147+
end
148+
149+
def default_for(thread)
150+
if @default_block
151+
raise "Cannot use default_for with default block"
144152
else
145153
@default
146154
end

lib/concurrent/atomic/thread_local_var.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module Concurrent
1111
# Creates a thread local variable.
1212
#
1313
# @param [Object] default the default value when otherwise unset
14+
# @param [Proc] block Optional block that gets called to obtain the
15+
# default value for each thread
1416

1517
# @!macro [new] thread_local_var_method_get
1618
#

spec/concurrent/atomic/thread_local_var_spec.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ module Concurrent
3535
expect(described_class.ancestors).to include(Concurrent::RubyThreadLocalVar)
3636
end
3737
end
38+
39+
it 'can set a block to be called to get the initial value' do
40+
v = described_class.new { 14 }
41+
expect(v.value).to eq 14
42+
end
43+
44+
context 'when attempting to set both an initial value and a block' do
45+
it do
46+
expect { described_class.new(14) { 14 } }.to raise_error(ArgumentError)
47+
end
48+
end
3849
end
3950

4051
context '#value' do
@@ -48,6 +59,36 @@ module Concurrent
4859
v.value = 2
4960
expect(v.value).to eq 2
5061
end
62+
63+
context 'when using a block to initialize the value' do
64+
it 'calls the block to initialize the value' do
65+
block = proc { }
66+
67+
expect(block).to receive(:call)
68+
69+
v = described_class.new(&block)
70+
v.value
71+
end
72+
73+
it 'sets the block return value as the current value' do
74+
value = 13
75+
76+
v = described_class.new { value += 1 }
77+
78+
v.value
79+
expect(v.value).to be 14
80+
end
81+
82+
it 'calls the block to initialize the value for each thread' do
83+
block = proc { }
84+
85+
expect(block).to receive(:call).twice
86+
87+
v = described_class.new(&block)
88+
Thread.new { v.value }.join
89+
Thread.new { v.value }.join
90+
end
91+
end
5192
end
5293

5394
context '#value=' do

0 commit comments

Comments
 (0)