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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -33
  3. data/lib/concurrent.rb +11 -3
  4. data/lib/concurrent/actor.rb +29 -29
  5. data/lib/concurrent/agent.rb +98 -16
  6. data/lib/concurrent/atomic.rb +125 -0
  7. data/lib/concurrent/channel.rb +36 -1
  8. data/lib/concurrent/condition.rb +67 -0
  9. data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
  10. data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
  11. data/lib/concurrent/count_down_latch.rb +60 -0
  12. data/lib/concurrent/dataflow.rb +85 -0
  13. data/lib/concurrent/dereferenceable.rb +69 -31
  14. data/lib/concurrent/event.rb +27 -21
  15. data/lib/concurrent/future.rb +103 -43
  16. data/lib/concurrent/ivar.rb +78 -0
  17. data/lib/concurrent/mvar.rb +154 -0
  18. data/lib/concurrent/obligation.rb +94 -9
  19. data/lib/concurrent/postable.rb +11 -9
  20. data/lib/concurrent/promise.rb +101 -127
  21. data/lib/concurrent/safe_task_executor.rb +28 -0
  22. data/lib/concurrent/scheduled_task.rb +60 -54
  23. data/lib/concurrent/stoppable.rb +2 -2
  24. data/lib/concurrent/supervisor.rb +36 -29
  25. data/lib/concurrent/thread_local_var.rb +117 -0
  26. data/lib/concurrent/timer_task.rb +28 -30
  27. data/lib/concurrent/utilities.rb +1 -1
  28. data/lib/concurrent/version.rb +1 -1
  29. data/spec/concurrent/agent_spec.rb +121 -230
  30. data/spec/concurrent/atomic_spec.rb +201 -0
  31. data/spec/concurrent/condition_spec.rb +171 -0
  32. data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
  33. data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
  34. data/spec/concurrent/count_down_latch_spec.rb +125 -0
  35. data/spec/concurrent/dataflow_spec.rb +160 -0
  36. data/spec/concurrent/dereferenceable_shared.rb +145 -0
  37. data/spec/concurrent/event_spec.rb +44 -9
  38. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
  39. data/spec/concurrent/future_spec.rb +184 -69
  40. data/spec/concurrent/ivar_spec.rb +192 -0
  41. data/spec/concurrent/mvar_spec.rb +380 -0
  42. data/spec/concurrent/obligation_spec.rb +193 -0
  43. data/spec/concurrent/observer_set_shared.rb +233 -0
  44. data/spec/concurrent/postable_shared.rb +3 -7
  45. data/spec/concurrent/promise_spec.rb +270 -192
  46. data/spec/concurrent/safe_task_executor_spec.rb +58 -0
  47. data/spec/concurrent/scheduled_task_spec.rb +142 -38
  48. data/spec/concurrent/thread_local_var_spec.rb +113 -0
  49. data/spec/concurrent/thread_pool_shared.rb +2 -3
  50. data/spec/concurrent/timer_task_spec.rb +31 -1
  51. data/spec/spec_helper.rb +2 -3
  52. data/spec/support/functions.rb +4 -0
  53. data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
  54. metadata +50 -30
  55. data/lib/concurrent/contract.rb +0 -21
  56. data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
  57. data/md/actor.md +0 -404
  58. data/md/agent.md +0 -142
  59. data/md/channel.md +0 -40
  60. data/md/dereferenceable.md +0 -49
  61. data/md/future.md +0 -125
  62. data/md/obligation.md +0 -32
  63. data/md/promise.md +0 -217
  64. data/md/scheduled_task.md +0 -156
  65. data/md/supervisor.md +0 -246
  66. data/md/thread_pool.md +0 -225
  67. data/md/timer_task.md +0 -191
  68. data/spec/concurrent/contract_spec.rb +0 -34
  69. 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,10 @@
1
+ require 'spec_helper'
2
+ require_relative 'observer_set_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe CopyOnNotifyObserverSet do
7
+ it_behaves_like 'an observer set'
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require_relative 'observer_set_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe CopyOnWriteObserverSet do
7
+ it_behaves_like 'an observer set'
8
+ end
9
+
10
+ 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