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,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