Skip to content

Commit 58cec34

Browse files
authored
Merge pull request #557 from ivoanjo/thread-local-block-initialization
Allow ThreadLocalVar to be initialized with a block
2 parents 0f9e82d + 025268b commit 58cec34

File tree

5 files changed

+91
-56
lines changed

5 files changed

+91
-56
lines changed

lib/concurrent/atomic/abstract_thread_local_var.rb

Lines changed: 29 additions & 3 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

@@ -25,7 +34,15 @@ def value=(value)
2534

2635
# @!macro thread_local_var_method_bind
2736
def bind(value, &block)
28-
raise NotImplementedError
37+
if block_given?
38+
old_value = self.value
39+
begin
40+
self.value = value
41+
yield
42+
ensure
43+
self.value = old_value
44+
end
45+
end
2946
end
3047

3148
protected
@@ -34,5 +51,14 @@ def bind(value, &block)
3451
def allocate_storage
3552
raise NotImplementedError
3653
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
3763
end
3864
end

lib/concurrent/atomic/java_thread_local_var.rb

Lines changed: 1 addition & 14 deletions
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
@@ -26,19 +26,6 @@ def value=(value)
2626
@var.set(value)
2727
end
2828

29-
# @!macro thread_local_var_method_bind
30-
def bind(value, &block)
31-
if block_given?
32-
old_value = @var.get
33-
begin
34-
@var.set(value)
35-
yield
36-
ensure
37-
@var.set(old_value)
38-
end
39-
end
40-
end
41-
4229
protected
4330

4431
# @!visibility private

lib/concurrent/atomic/ruby_thread_local_var.rb

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,19 @@ class RubyThreadLocalVar < AbstractThreadLocalVar
3535
@@next = 0
3636
private_constant :FREE, :LOCK, :ARRAYS
3737

38-
# @!macro [attach] thread_local_var_method_initialize
39-
#
40-
# Creates a thread local variable.
41-
#
42-
# @param [Object] default the default value when otherwise unset
43-
def initialize(default = nil)
44-
@default = default
45-
allocate_storage
46-
end
47-
4838
# @!macro thread_local_var_method_get
4939
def value
5040
if array = get_threadlocal_array
5141
value = array[@index]
5242
if value.nil?
53-
@default
43+
default
5444
elsif value.equal?(NULL)
5545
nil
5646
else
5747
value
5848
end
5949
else
60-
@default
50+
default
6151
end
6252
end
6353

@@ -76,19 +66,6 @@ def value=(value)
7666
value
7767
end
7868

79-
# @!macro thread_local_var_method_bind
80-
def bind(value, &block)
81-
if block_given?
82-
old_value = self.value
83-
begin
84-
self.value = value
85-
yield
86-
ensure
87-
self.value = old_value
88-
end
89-
end
90-
end
91-
9269
protected
9370

9471
# @!visibility private
@@ -158,12 +135,20 @@ def value_for(thread)
158135
if array = get_threadlocal_array(thread)
159136
value = array[@index]
160137
if value.nil?
161-
@default
138+
default_for(thread)
162139
elsif value.equal?(NULL)
163140
nil
164141
else
165142
value
166143
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"
167152
else
168153
@default
169154
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: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,20 @@ module Concurrent
66

77
describe ThreadLocalVar do
88

9-
subject { ThreadLocalVar.new }
10-
119
context '#initialize' do
1210

1311
it 'can set an initial value' do
14-
v = ThreadLocalVar.new(14)
12+
v = described_class.new(14)
1513
expect(v.value).to eq 14
1614
end
1715

1816
it 'sets nil as a default initial value' do
19-
v = ThreadLocalVar.new
17+
v = described_class.new
2018
expect(v.value).to be_nil
2119
end
2220

2321
it 'sets the same initial value for all threads' do
24-
v = ThreadLocalVar.new(14)
22+
v = described_class.new(14)
2523
t1 = Thread.new { v.value }
2624
t2 = Thread.new { v.value }
2725
expect(t1.value).to eq 14
@@ -30,51 +28,88 @@ module Concurrent
3028

3129
if Concurrent.on_jruby?
3230
it 'extends JavaThreadLocalVar' do
33-
expect(subject.class.ancestors).to include(Concurrent::JavaThreadLocalVar)
31+
expect(described_class.ancestors).to include(Concurrent::JavaThreadLocalVar)
3432
end
3533
else
3634
it 'extends RubyThreadLocalVar' do
37-
expect(subject.class.ancestors).to include(Concurrent::RubyThreadLocalVar)
35+
expect(described_class.ancestors).to include(Concurrent::RubyThreadLocalVar)
36+
end
37+
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)
3847
end
3948
end
4049
end
4150

4251
context '#value' do
52+
let(:v) { described_class.new(14) }
4353

4454
it 'returns the current value' do
45-
v = ThreadLocalVar.new(14)
4655
expect(v.value).to eq 14
4756
end
4857

4958
it 'returns the value after modification' do
50-
v = ThreadLocalVar.new(14)
5159
v.value = 2
5260
expect(v.value).to eq 2
5361
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
5492
end
5593

5694
context '#value=' do
95+
let(:v) { described_class.new(14) }
5796

5897
it 'sets a new value' do
59-
v = ThreadLocalVar.new(14)
6098
v.value = 2
6199
expect(v.value).to eq 2
62100
end
63101

64102
it 'returns the new value' do
65-
v = ThreadLocalVar.new(14)
66103
expect(v.value = 2).to eq 2
67104
end
68105

69106
it 'does not modify the initial value for other threads' do
70-
v = ThreadLocalVar.new(14)
71107
v.value = 2
72108
t = Thread.new { v.value }
73109
expect(t.value).to eq 14
74110
end
75111

76112
it 'does not modify the value for other threads' do
77-
v = ThreadLocalVar.new(14)
78113
v.value = 2
79114

80115
b1 = CountDownLatch.new(2)

0 commit comments

Comments
 (0)