concurrent-ruby 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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,192 @@
1
+ require 'spec_helper'
2
+ require_relative 'dereferenceable_shared'
3
+ require_relative 'obligation_shared'
4
+ require_relative 'uses_global_thread_pool_shared'
5
+
6
+ module Concurrent
7
+
8
+ describe IVar do
9
+
10
+ let!(:value) { 10 }
11
+
12
+ subject do
13
+ i = IVar.new
14
+ i.set(14)
15
+ i
16
+ end
17
+
18
+ context 'behavior' do
19
+
20
+ # obligation
21
+
22
+ let!(:fulfilled_value) { 10 }
23
+ let(:rejected_reason) { StandardError.new('Boom!') }
24
+
25
+ let(:pending_subject) do
26
+ @i = IVar.new
27
+ Thread.new do
28
+ sleep(3)
29
+ @i.set(fulfilled_value)
30
+ end
31
+ @i
32
+ end
33
+
34
+ let(:fulfilled_subject) do
35
+ i = IVar.new
36
+ i.set(fulfilled_value)
37
+ i
38
+ end
39
+
40
+ let(:rejected_subject) do
41
+ i = IVar.new
42
+ i.fail(rejected_reason)
43
+ i
44
+ end
45
+
46
+ it_should_behave_like :obligation
47
+
48
+ # dereferenceable
49
+
50
+ def dereferenceable_subject(value, opts = {})
51
+ IVar.new(value, opts)
52
+ end
53
+
54
+ def dereferenceable_observable(opts = {})
55
+ IVar.new(IVar::NO_VALUE, opts)
56
+ end
57
+
58
+ def execute_dereferenceable(subject)
59
+ subject.set('value')
60
+ sleep(0.1)
61
+ end
62
+
63
+ it_should_behave_like :dereferenceable
64
+ end
65
+
66
+ context '#initialize' do
67
+
68
+ it 'does not have to set an initial value' do
69
+ i = IVar.new
70
+ i.should be_incomplete
71
+ end
72
+
73
+ it 'does not set an initial value if you pass NO_VALUE' do
74
+ i = IVar.new(IVar::NO_VALUE)
75
+ i.should be_incomplete
76
+ end
77
+
78
+ it 'can set an initial value' do
79
+ i = IVar.new(14)
80
+ i.should be_completed
81
+ end
82
+
83
+ end
84
+
85
+ context '#set' do
86
+
87
+ it 'sets the state to be fulfilled' do
88
+ i = IVar.new
89
+ i.set(14)
90
+ i.should be_fulfilled
91
+ end
92
+
93
+ it 'sets the value' do
94
+ i = IVar.new
95
+ i.set(14)
96
+ i.value.should eq 14
97
+ end
98
+
99
+ it 'raises an exception if set more than once' do
100
+ i = IVar.new
101
+ i.set(14)
102
+ expect {i.set(2)}.to raise_error(MultipleAssignmentError)
103
+ i.value.should eq 14
104
+ end
105
+
106
+ end
107
+
108
+ context '#fail' do
109
+
110
+ it 'sets the state to be rejected' do
111
+ i = IVar.new
112
+ i.fail
113
+ i.should be_rejected
114
+ end
115
+
116
+ it 'sets the value to be nil' do
117
+ i = IVar.new
118
+ i.fail
119
+ i.value.should be_nil
120
+ end
121
+
122
+ it 'raises an exception if set more than once' do
123
+ i = IVar.new
124
+ i.fail
125
+ expect {i.fail}.to raise_error(MultipleAssignmentError)
126
+ i.value.should be_nil
127
+ end
128
+
129
+ end
130
+
131
+ context 'observation' do
132
+
133
+ let(:clazz) do
134
+ Class.new do
135
+ attr_reader :value
136
+ attr_reader :reason
137
+ attr_reader :count
138
+ define_method(:update) do |time, value, reason|
139
+ @count = @count.to_i + 1
140
+ @value = value
141
+ @reason = reason
142
+ end
143
+ end
144
+ end
145
+
146
+ let(:observer) { clazz.new }
147
+
148
+ it 'notifies all observers on #set' do
149
+ i = IVar.new
150
+ i.add_observer(observer)
151
+
152
+ i.set(42)
153
+
154
+ observer.value.should == 42
155
+ observer.reason.should be_nil
156
+ end
157
+
158
+ context 'deadlock avoidance' do
159
+
160
+ def reentrant_observer(i)
161
+ obs = Object.new
162
+ obs.define_singleton_method(:update) do |time, value, reason|
163
+ @value = i.value
164
+ end
165
+ obs.define_singleton_method(:value) { @value }
166
+ obs
167
+ end
168
+
169
+ it 'should notify observers outside mutex lock' do
170
+ i = IVar.new
171
+ obs = reentrant_observer(i)
172
+
173
+ i.add_observer(obs)
174
+ i.set(42)
175
+
176
+ obs.value.should eq 42
177
+ end
178
+
179
+ it 'should notify a new observer added after fulfillment outside lock' do
180
+ i = IVar.new
181
+ i.set(42)
182
+ obs = reentrant_observer(i)
183
+
184
+ i.add_observer(obs)
185
+
186
+ obs.value.should eq 42
187
+ end
188
+ end
189
+
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,380 @@
1
+ require 'spec_helper'
2
+ require_relative 'dereferenceable_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe MVar do
7
+
8
+ context 'behavior' do
9
+
10
+ # dereferenceable
11
+
12
+ def dereferenceable_subject(value, opts = {})
13
+ MVar.new(value, opts)
14
+ end
15
+
16
+ it_should_behave_like :dereferenceable
17
+ end
18
+
19
+ describe '#initialize' do
20
+
21
+ it 'accepts no initial value' do
22
+ m = MVar.new
23
+ m.should be_empty
24
+ end
25
+
26
+ it 'accepts an empty initial value' do
27
+ m = MVar.new(MVar::EMPTY)
28
+ m.should be_empty
29
+ end
30
+
31
+ it 'accepts an initial value' do
32
+ m = MVar.new(14)
33
+ m.should_not be_empty
34
+ end
35
+
36
+ it 'accepts a nil initial value' do
37
+ m = MVar.new(nil)
38
+ m.should_not be_empty
39
+ end
40
+
41
+ end
42
+
43
+ describe '#take' do
44
+
45
+ it 'sets the MVar to empty' do
46
+ m = MVar.new(14)
47
+ m.take
48
+ m.should be_empty
49
+ end
50
+
51
+ it 'returns the value on a full MVar' do
52
+ m = MVar.new(14)
53
+ m.take.should eq 14
54
+ end
55
+
56
+ it 'waits for another thread to #put' do
57
+ m = MVar.new
58
+
59
+ putter = Thread.new {
60
+ sleep(0.1)
61
+ m.put 14
62
+ }
63
+
64
+ m.take.should eq 14
65
+ end
66
+
67
+ it 'returns TIMEOUT on timeout on an empty MVar' do
68
+ m = MVar.new
69
+ m.take(0.1).should eq MVar::TIMEOUT
70
+ end
71
+
72
+ end
73
+
74
+ describe '#put' do
75
+
76
+ it 'sets the MVar to be empty' do
77
+ m = MVar.new(14)
78
+ m.take
79
+ m.should be_empty
80
+ end
81
+
82
+ it 'sets a new value on an empty MVar' do
83
+ m = MVar.new
84
+ m.put 14
85
+ m.take.should eq 14
86
+ end
87
+
88
+ it 'waits for another thread to #take' do
89
+ m = MVar.new(14)
90
+
91
+ putter = Thread.new {
92
+ sleep(0.1)
93
+ m.take
94
+ }
95
+
96
+ m.put(14).should eq 14
97
+ end
98
+
99
+ it 'returns TIMEOUT on timeout on a full MVar' do
100
+ m = MVar.new(14)
101
+ m.put(14, 0.1).should eq MVar::TIMEOUT
102
+ end
103
+
104
+ it 'returns the value' do
105
+ m = MVar.new
106
+ m.put(14).should eq 14
107
+ end
108
+
109
+ end
110
+
111
+ describe '#empty?' do
112
+
113
+ it 'returns true on an empty MVar' do
114
+ m = MVar.new
115
+ m.should be_empty
116
+ end
117
+
118
+ it 'returns false on a full MVar' do
119
+ m = MVar.new(14)
120
+ m.should_not be_empty
121
+ end
122
+
123
+ end
124
+
125
+ describe '#full?' do
126
+
127
+ it 'returns false on an empty MVar' do
128
+ m = MVar.new
129
+ m.should_not be_full
130
+ end
131
+
132
+ it 'returns true on a full MVar' do
133
+ m = MVar.new(14)
134
+ m.should be_full
135
+ end
136
+
137
+ end
138
+
139
+ describe '#modify' do
140
+
141
+ it 'raises an exception when no block given' do
142
+ m = MVar.new(14)
143
+ expect { m.modify }.to raise_error(ArgumentError)
144
+ end
145
+
146
+ it 'modifies a full MVar' do
147
+ m = MVar.new(14)
148
+ m.modify{ |v| v + 2 }
149
+ m.take.should eq 16
150
+ end
151
+
152
+ it 'returns the unmodified value' do
153
+ m = MVar.new(14)
154
+ m.modify{ |v| v + 2 }.should eq 14
155
+ end
156
+
157
+ it 'waits for another thread to #put' do
158
+ m = MVar.new
159
+
160
+ putter = Thread.new {
161
+ sleep(0.1)
162
+ m.put 14
163
+ }
164
+
165
+ m.modify{ |v| v + 2 }.should eq 14
166
+ end
167
+
168
+ it 'is atomic' do
169
+ m = MVar.new(0)
170
+
171
+ # #modify conceptually does #take and #put - but it should be atomic.
172
+ # Check that another #put can't sneak it during the #modify.
173
+
174
+ modifier = Thread.new {
175
+ m.modify do |v|
176
+ sleep(0.5)
177
+ 1
178
+ end
179
+ }
180
+
181
+ sleep(0.1)
182
+ m.put(2, 0.5).should eq MVar::TIMEOUT
183
+ m.take.should eq 1
184
+ end
185
+
186
+ it 'returns TIMEOUT on timeout on an empty MVar' do
187
+ m = MVar.new
188
+ m.modify(0.1){ |v| v + 2 }.should eq MVar::TIMEOUT
189
+ end
190
+
191
+ end
192
+
193
+ describe '#try_put!' do
194
+
195
+ it 'returns true an empty MVar' do
196
+ m = MVar.new
197
+ m.try_put!(14).should eq true
198
+ end
199
+
200
+ it 'returns false on a full MVar' do
201
+ m = MVar.new(14)
202
+ m.try_put!(14).should eq false
203
+ end
204
+
205
+ it 'sets an empty MVar to be full' do
206
+ m = MVar.new
207
+ m.try_put! 14
208
+ m.should be_full
209
+ end
210
+
211
+ end
212
+
213
+ describe '#try_take!' do
214
+
215
+ it 'returns EMPTY an empty MVar' do
216
+ m = MVar.new
217
+ m.try_take!.should eq MVar::EMPTY
218
+ end
219
+
220
+ it 'returns the value on a full MVar' do
221
+ m = MVar.new(14)
222
+ m.try_take!.should eq 14
223
+ end
224
+
225
+ it 'sets a full MVar to be empty' do
226
+ m = MVar.new(14)
227
+ m.try_take!
228
+ m.should be_empty
229
+ end
230
+
231
+ end
232
+
233
+ describe '#set!' do
234
+
235
+ it 'sets an empty MVar to be full' do
236
+ m = MVar.new
237
+ m.set! 14
238
+ m.should be_full
239
+ end
240
+
241
+ it 'sets a full MVar to be full' do
242
+ m = MVar.new(2)
243
+ m.set! 14
244
+ m.should be_full
245
+ m.take.should eq 14
246
+ end
247
+
248
+ it 'returns EMPTY on an empty MVar' do
249
+ m = MVar.new
250
+ m.set!(2).should eq MVar::EMPTY
251
+ end
252
+
253
+ it 'returns the original value on a full MVar' do
254
+ m = MVar.new(14)
255
+ m.set!(2).should eq 14
256
+ end
257
+
258
+ end
259
+
260
+ describe '#modify!' do
261
+
262
+ it 'raises an exception when no block given' do
263
+ m = MVar.new(14)
264
+ expect { m.modify! }.to raise_error(ArgumentError)
265
+ end
266
+
267
+ it 'modifies a full MVar' do
268
+ m = MVar.new(14)
269
+ m.modify!{ |v| v + 2 }
270
+ m.take.should eq 16
271
+ end
272
+
273
+ it 'modifies an empty MVar' do
274
+ m = MVar.new
275
+ m.modify!{ |v| 14 }
276
+ m.take.should eq 14
277
+ end
278
+
279
+ it 'can be used to set a full MVar to empty' do
280
+ m = MVar.new(14)
281
+ m.modify!{ |v| MVar::EMPTY }
282
+ m.should be_empty
283
+ end
284
+
285
+ it 'can be used to set an empty MVar to empty' do
286
+ m = MVar.new
287
+ m.modify!{ |v| MVar::EMPTY }
288
+ m.should be_empty
289
+ end
290
+
291
+ it 'returns the unmodified value' do
292
+ m = MVar.new(14)
293
+ m.modify!{ |v| v + 2 }.should eq 14
294
+ end
295
+
296
+ end
297
+
298
+ context 'spurious wake ups' do
299
+
300
+ let(:m) { MVar.new }
301
+
302
+ before(:each) do
303
+ def m.simulate_spurious_wake_up
304
+ @mutex.synchronize do
305
+ @full_condition.broadcast
306
+ @empty_condition.broadcast
307
+ end
308
+ end
309
+ end
310
+
311
+ describe '#take' do
312
+ it 'waits for another thread to #put' do
313
+ Thread.new { sleep(0.5); m.put 14 }
314
+ Thread.new { sleep(0.1); m.simulate_spurious_wake_up }
315
+
316
+ m.take.should eq 14
317
+ end
318
+
319
+ it 'returns TIMEOUT on timeout on an empty MVar' do
320
+ result = nil
321
+ Thread.new { result = m.take(0.3) }
322
+ sleep(0.1)
323
+ Thread.new { m.simulate_spurious_wake_up }
324
+ sleep(0.1)
325
+ result.should be_nil
326
+ sleep(0.2)
327
+ result.should eq MVar::TIMEOUT
328
+ end
329
+ end
330
+
331
+ describe '#modify' do
332
+
333
+ it 'waits for another thread to #put' do
334
+ Thread.new { sleep(0.5); m.put 14 }
335
+ Thread.new { sleep(0.1); m.simulate_spurious_wake_up }
336
+
337
+ m.modify{ |v| v + 2 }.should eq 14
338
+ end
339
+
340
+ it 'returns TIMEOUT on timeout on an empty MVar' do
341
+ result = nil
342
+ Thread.new { result = m.modify(0.3){ |v| v + 2 } }
343
+ sleep(0.1)
344
+ Thread.new { m.simulate_spurious_wake_up }
345
+ sleep(0.1)
346
+ result.should be_nil
347
+ sleep(0.2)
348
+ result.should eq MVar::TIMEOUT
349
+ end
350
+ end
351
+
352
+ describe '#put' do
353
+
354
+ before(:each) { m.put(42) }
355
+
356
+ it 'waits for another thread to #take' do
357
+ Thread.new { sleep(0.5); m.take }
358
+ Thread.new { sleep(0.1); m.simulate_spurious_wake_up }
359
+
360
+ m.put(14).should eq 14
361
+ end
362
+
363
+ it 'returns TIMEOUT on timeout on a full MVar' do
364
+ result = nil
365
+ Thread.new { result = m.put(14, 0.3) }
366
+ sleep(0.1)
367
+ Thread.new { m.simulate_spurious_wake_up }
368
+ sleep(0.1)
369
+ result.should be_nil
370
+ sleep(0.2)
371
+ result.should eq MVar::TIMEOUT
372
+ end
373
+ end
374
+
375
+
376
+ end
377
+
378
+ end
379
+
380
+ end