concurrent-ruby 0.4.1 → 0.5.0.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe AtomicFixnum do
|
6
|
+
|
7
|
+
context 'construction' do
|
8
|
+
|
9
|
+
it 'sets the initial value' do
|
10
|
+
AtomicFixnum.new(10).value.should eq 10
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'defaults the initial value to zero' do
|
14
|
+
AtomicFixnum.new.value.should eq 0
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raises en exception if the initial value is not a Fixnum' do
|
18
|
+
lambda {
|
19
|
+
AtomicFixnum.new(10.01)
|
20
|
+
}.should raise_error(ArgumentError)
|
21
|
+
end
|
22
|
+
|
23
|
+
if jruby?
|
24
|
+
|
25
|
+
it 'uses Java AtomicLong' do
|
26
|
+
mutex = double('mutex')
|
27
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
28
|
+
mutex.should_not_receive(:synchronize)
|
29
|
+
AtomicFixnum.new.value
|
30
|
+
end
|
31
|
+
|
32
|
+
else
|
33
|
+
|
34
|
+
it 'is synchronized' do
|
35
|
+
mutex = double('mutex')
|
36
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
37
|
+
mutex.should_receive(:synchronize)
|
38
|
+
AtomicFixnum.new.value
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
context '#value' do
|
46
|
+
|
47
|
+
it 'returns the current value' do
|
48
|
+
counter = AtomicFixnum.new(10)
|
49
|
+
counter.value.should eq 10
|
50
|
+
counter.increment
|
51
|
+
counter.value.should eq 11
|
52
|
+
counter.decrement
|
53
|
+
counter.value.should eq 10
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context '#value=' do
|
58
|
+
|
59
|
+
it 'sets the #value to the given `Fixnum`' do
|
60
|
+
atomic = AtomicFixnum.new(0)
|
61
|
+
atomic.value = 10
|
62
|
+
atomic.value.should eq 10
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns the new value' do
|
66
|
+
atomic = AtomicFixnum.new(0)
|
67
|
+
(atomic.value = 10).should eq 10
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'raises and exception if the value is not a `Fixnum`' do
|
71
|
+
atomic = AtomicFixnum.new(0)
|
72
|
+
expect {
|
73
|
+
atomic.value = 'foo'
|
74
|
+
}.to raise_error(ArgumentError)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context '#increment' do
|
79
|
+
|
80
|
+
it 'increases the value by one' do
|
81
|
+
counter = AtomicFixnum.new(10)
|
82
|
+
3.times{ counter.increment }
|
83
|
+
counter.value.should eq 13
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'returns the new value' do
|
87
|
+
counter = AtomicFixnum.new(10)
|
88
|
+
counter.increment.should eq 11
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'is aliased as #up' do
|
92
|
+
AtomicFixnum.new(10).up.should eq 11
|
93
|
+
end
|
94
|
+
|
95
|
+
if jruby?
|
96
|
+
|
97
|
+
it 'does not use Mutex class' do
|
98
|
+
mutex = double('mutex')
|
99
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
100
|
+
mutex.should_not_receive(:synchronize)
|
101
|
+
AtomicFixnum.new.increment
|
102
|
+
end
|
103
|
+
|
104
|
+
else
|
105
|
+
|
106
|
+
it 'is synchronized' do
|
107
|
+
mutex = double('mutex')
|
108
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
109
|
+
mutex.should_receive(:synchronize)
|
110
|
+
AtomicFixnum.new.increment
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
context '#decrement' do
|
118
|
+
|
119
|
+
it 'decreases the value by one' do
|
120
|
+
counter = AtomicFixnum.new(10)
|
121
|
+
3.times{ counter.decrement }
|
122
|
+
counter.value.should eq 7
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns the new value' do
|
126
|
+
counter = AtomicFixnum.new(10)
|
127
|
+
counter.decrement.should eq 9
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'is aliased as #down' do
|
131
|
+
AtomicFixnum.new(10).down.should eq 9
|
132
|
+
end
|
133
|
+
|
134
|
+
if jruby?
|
135
|
+
|
136
|
+
it 'does not use Mutex class' do
|
137
|
+
mutex = double('mutex')
|
138
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
139
|
+
mutex.should_not_receive(:synchronize)
|
140
|
+
AtomicFixnum.new.decrement
|
141
|
+
end
|
142
|
+
|
143
|
+
else
|
144
|
+
|
145
|
+
it 'is synchronized' do
|
146
|
+
mutex = double('mutex')
|
147
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
148
|
+
mutex.should_receive(:synchronize)
|
149
|
+
AtomicFixnum.new.decrement
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
context '#compare_and_set' do
|
157
|
+
|
158
|
+
it 'returns false if the value is not found' do
|
159
|
+
AtomicFixnum.new(14).compare_and_set(2, 14).should eq false
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'returns true if the value is found' do
|
163
|
+
AtomicFixnum.new(14).compare_and_set(14, 2).should eq true
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'sets if the value is found' do
|
167
|
+
f = AtomicFixnum.new(14)
|
168
|
+
f.compare_and_set(14, 2)
|
169
|
+
f.value.should eq 2
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'does not set if the value is not found' do
|
173
|
+
f = AtomicFixnum.new(14)
|
174
|
+
f.compare_and_set(2, 12)
|
175
|
+
f.value.should eq 14
|
176
|
+
end
|
177
|
+
|
178
|
+
if jruby?
|
179
|
+
|
180
|
+
it 'does not use Mutex class' do
|
181
|
+
mutex = double('mutex')
|
182
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
183
|
+
mutex.should_not_receive(:synchronize)
|
184
|
+
AtomicFixnum.new(14).compare_and_set(14, 2)
|
185
|
+
end
|
186
|
+
|
187
|
+
else
|
188
|
+
|
189
|
+
it 'is synchronized' do
|
190
|
+
mutex = double('mutex')
|
191
|
+
Mutex.stub(:new).with(no_args).and_return(mutex)
|
192
|
+
mutex.should_receive(:synchronize)
|
193
|
+
AtomicFixnum.new(14).compare_and_set(14, 2)
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe Condition do
|
6
|
+
|
7
|
+
let(:mutex) { Mutex.new }
|
8
|
+
subject{ Condition.new }
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
# rspec is not thread safe, without mutex initialization
|
12
|
+
# we can experience race conditions
|
13
|
+
mutex
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with no waiting threads' do
|
17
|
+
describe '#signal' do
|
18
|
+
it 'should return immediately' do
|
19
|
+
subject.signal.should be_true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#broadcast' do
|
24
|
+
it 'should return immediately' do
|
25
|
+
subject.broadcast.should be_true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with one waiting thread' do
|
31
|
+
|
32
|
+
context 'signalled wake up' do
|
33
|
+
|
34
|
+
describe '#wait without timeout' do
|
35
|
+
|
36
|
+
it 'should block the thread' do
|
37
|
+
t = Thread.new { mutex.synchronize { subject.wait(mutex) } }
|
38
|
+
sleep(0.1)
|
39
|
+
t.status.should eq 'sleep'
|
40
|
+
t.kill
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should return a woken up result when is woken up by #signal' do
|
44
|
+
result = nil
|
45
|
+
t = Thread.new { mutex.synchronize { result = subject.wait(mutex) } }
|
46
|
+
sleep(0.1)
|
47
|
+
mutex.synchronize { subject.signal }
|
48
|
+
sleep(0.1)
|
49
|
+
result.should be_woken_up
|
50
|
+
result.should_not be_timed_out
|
51
|
+
result.remaining_time.should be_nil
|
52
|
+
t.status.should be_false
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should return a woken up result when is woken up by #broadcast' do
|
56
|
+
result = nil
|
57
|
+
t = Thread.new { mutex.synchronize { result = subject.wait(mutex) } }
|
58
|
+
sleep(0.1)
|
59
|
+
mutex.synchronize { subject.broadcast }
|
60
|
+
sleep(0.1)
|
61
|
+
result.should be_woken_up
|
62
|
+
result.should_not be_timed_out
|
63
|
+
result.remaining_time.should be_nil
|
64
|
+
t.status.should be_false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'timeout' do
|
71
|
+
|
72
|
+
describe '#wait' do
|
73
|
+
|
74
|
+
it 'should block the thread' do
|
75
|
+
t = Thread.new { mutex.synchronize { subject.wait(mutex, 1) } }
|
76
|
+
sleep(0.1)
|
77
|
+
t.status.should eq 'sleep'
|
78
|
+
t.kill
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return remaining time when is woken up by #signal' do
|
82
|
+
result = nil
|
83
|
+
t = Thread.new { mutex.synchronize { result = subject.wait(mutex, 1) } }
|
84
|
+
sleep(0.1)
|
85
|
+
mutex.synchronize { subject.signal }
|
86
|
+
sleep(0.1)
|
87
|
+
result.should be_woken_up
|
88
|
+
result.should_not be_timed_out
|
89
|
+
result.remaining_time.should be_within(0.05).of(0.87)
|
90
|
+
t.status.should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should return remaining time when is woken up by #broadcast' do
|
94
|
+
result = nil
|
95
|
+
t = Thread.new { mutex.synchronize { result = subject.wait(mutex, 1) } }
|
96
|
+
sleep(0.1)
|
97
|
+
mutex.synchronize { subject.broadcast }
|
98
|
+
sleep(0.1)
|
99
|
+
result.should be_woken_up
|
100
|
+
result.should_not be_timed_out
|
101
|
+
result.remaining_time.should be_within(0.05).of(0.87)
|
102
|
+
t.status.should be_false
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return 0 or negative number if timed out' do
|
106
|
+
result = nil
|
107
|
+
t = Thread.new { mutex.synchronize { result = subject.wait(mutex, 0.1) } }
|
108
|
+
sleep(0.2)
|
109
|
+
result.should_not be_woken_up
|
110
|
+
result.should be_timed_out
|
111
|
+
result.remaining_time.should be_less_than_or_equal_to(0)
|
112
|
+
t.status.should be_false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'with many waiting threads' do
|
120
|
+
|
121
|
+
context 'signalled wake up' do
|
122
|
+
|
123
|
+
describe '#wait' do
|
124
|
+
|
125
|
+
it 'should block threads' do
|
126
|
+
t1 = Thread.new { mutex.synchronize { subject.wait(mutex) } }
|
127
|
+
t2 = Thread.new { mutex.synchronize { subject.wait(mutex) } }
|
128
|
+
sleep(0.1)
|
129
|
+
[t1, t2].each { |t| t.status.should eq 'sleep' }
|
130
|
+
[t1, t2].each { |t| t.kill }
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#signal' do
|
136
|
+
it 'wakes up only one thread' do
|
137
|
+
latch = CountDownLatch.new(2)
|
138
|
+
|
139
|
+
t1 = Thread.new { mutex.synchronize { subject.wait(mutex); latch.count_down } }
|
140
|
+
t2 = Thread.new { mutex.synchronize { subject.wait(mutex); latch.count_down } }
|
141
|
+
|
142
|
+
sleep(0.1)
|
143
|
+
mutex.synchronize { subject.signal }
|
144
|
+
sleep(0.2)
|
145
|
+
|
146
|
+
latch.count.should eq 1
|
147
|
+
[t1, t2].each { |t| t.kill }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '#broadcast' do
|
152
|
+
it 'wakes up all threads' do
|
153
|
+
latch = CountDownLatch.new(2)
|
154
|
+
|
155
|
+
t1 = Thread.new { mutex.synchronize { subject.wait(mutex); latch.count_down } }
|
156
|
+
t2 = Thread.new { mutex.synchronize { subject.wait(mutex); latch.count_down } }
|
157
|
+
|
158
|
+
sleep(0.1)
|
159
|
+
mutex.synchronize { subject.broadcast }
|
160
|
+
sleep(0.2)
|
161
|
+
|
162
|
+
latch.count.should eq 0
|
163
|
+
[t1, t2].each { |t| t.kill }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe CountDownLatch do
|
6
|
+
|
7
|
+
let(:latch) { CountDownLatch.new(3) }
|
8
|
+
let(:zero_count_latch) { CountDownLatch.new(0) }
|
9
|
+
|
10
|
+
context '#initialize' do
|
11
|
+
|
12
|
+
it 'raises an exception if the initial count is less than zero' do
|
13
|
+
expect {
|
14
|
+
CountDownLatch.new(-1)
|
15
|
+
}.to raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises an exception if the initial count is not an integer' do
|
19
|
+
expect {
|
20
|
+
CountDownLatch.new('foo')
|
21
|
+
}.to raise_error(ArgumentError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#count' do
|
26
|
+
|
27
|
+
it 'should be the value passed to the constructor' do
|
28
|
+
latch.count.should eq 3
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should be decreased after every count down' do
|
32
|
+
latch.count_down
|
33
|
+
latch.count.should eq 2
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should not go below zero' do
|
37
|
+
5.times { latch.count_down }
|
38
|
+
latch.count.should eq 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#wait' do
|
43
|
+
|
44
|
+
context 'count set to zero' do
|
45
|
+
it 'should return true immediately' do
|
46
|
+
result = zero_count_latch.wait
|
47
|
+
result.should be_true
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should return true immediately with timeout' do
|
51
|
+
result = zero_count_latch.wait(5)
|
52
|
+
result.should be_true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'non zero count' do
|
57
|
+
|
58
|
+
it 'should block thread until counter is set to zero' do
|
59
|
+
3.times do
|
60
|
+
Thread.new { sleep(0.1); latch.count_down }
|
61
|
+
end
|
62
|
+
|
63
|
+
result = latch.wait
|
64
|
+
result.should be_true
|
65
|
+
latch.count.should eq 0
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should block until counter is set to zero with timeout' do
|
69
|
+
3.times do
|
70
|
+
Thread.new { sleep(0.1); latch.count_down }
|
71
|
+
end
|
72
|
+
|
73
|
+
result = latch.wait(1)
|
74
|
+
result.should be_true
|
75
|
+
latch.count.should eq 0
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should block until timeout and return false when counter is not set to zero' do
|
80
|
+
result = latch.wait(0.1)
|
81
|
+
result.should be_false
|
82
|
+
latch.count.should eq 3
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'spurious wake ups' do
|
88
|
+
|
89
|
+
before(:each) do
|
90
|
+
def latch.simulate_spurious_wake_up
|
91
|
+
@mutex.synchronize do
|
92
|
+
@condition.signal
|
93
|
+
@condition.broadcast
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should resist to spurious wake ups without timeout' do
|
99
|
+
@expected = false
|
100
|
+
Thread.new { latch.wait; @expected = true }
|
101
|
+
|
102
|
+
sleep(0.1)
|
103
|
+
latch.simulate_spurious_wake_up
|
104
|
+
|
105
|
+
sleep(0.1)
|
106
|
+
@expected.should be_false
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should resist to spurious wake ups with timeout' do
|
110
|
+
@expected = false
|
111
|
+
Thread.new { latch.wait(0.5); @expected = true }
|
112
|
+
|
113
|
+
sleep(0.1)
|
114
|
+
latch.simulate_spurious_wake_up
|
115
|
+
|
116
|
+
sleep(0.1)
|
117
|
+
@expected.should be_false
|
118
|
+
|
119
|
+
sleep(0.4)
|
120
|
+
@expected.should be_true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|