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,58 @@
1
+ require 'spec_helper'
2
+ require 'concurrent/safe_task_executor'
3
+
4
+ module Concurrent
5
+
6
+ describe SafeTaskExecutor do
7
+
8
+ describe '#execute' do
9
+
10
+ context 'happy execution' do
11
+
12
+ let(:task) { Proc.new { 42 } }
13
+ let(:executor) { SafeTaskExecutor.new(task) }
14
+
15
+ it 'should return success' do
16
+ success, value, reason = executor.execute
17
+ success.should be_true
18
+ end
19
+
20
+ it 'should return task value' do
21
+ success, value, reason = executor.execute
22
+ value.should eq 42
23
+ end
24
+
25
+ it 'should return a nil reason' do
26
+ success, value, reason = executor.execute
27
+ reason.should be_nil
28
+ end
29
+
30
+ end
31
+
32
+ context 'failing execution' do
33
+
34
+ let(:task) { Proc.new { raise StandardError.new('an error') } }
35
+ let(:executor) { SafeTaskExecutor.new(task) }
36
+
37
+ it 'should return false success' do
38
+ success, value, reason = executor.execute
39
+ success.should be_false
40
+ end
41
+
42
+ it 'should return a nil value' do
43
+ success, value, reason = executor.execute
44
+ value.should be_nil
45
+ end
46
+
47
+ it 'should return the reason' do
48
+ success, value, reason = executor.execute
49
+ reason.should be_a(StandardError)
50
+ reason.message.should eq 'an error'
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'timecop'
3
+ require_relative 'dereferenceable_shared'
3
4
  require_relative 'obligation_shared'
4
5
 
5
6
  module Concurrent
@@ -14,77 +15,183 @@ module Concurrent
14
15
  let!(:rejected_reason) { StandardError.new('mojo jojo') }
15
16
 
16
17
  let(:pending_subject) do
17
- ScheduledTask.new(1){ fulfilled_value }
18
+ ScheduledTask.new(1){ fulfilled_value }.execute
18
19
  end
19
20
 
20
21
  let(:fulfilled_subject) do
21
- ScheduledTask.new(0.1){ fulfilled_value }.tap(){ sleep(0.2) }
22
+ ScheduledTask.new(0.1){ fulfilled_value }.execute.tap{ sleep(0.2) }
22
23
  end
23
24
 
24
25
  let(:rejected_subject) do
25
- ScheduledTask.new(0.1){ raise rejected_reason }.tap(){ sleep(0.2) }
26
+ ScheduledTask.new(0.1){ raise rejected_reason }.execute.tap{ sleep(0.2) }
26
27
  end
27
28
 
28
29
  it_should_behave_like :obligation
30
+
31
+ # dereferenceable
32
+
33
+ def dereferenceable_subject(value, opts = {})
34
+ ScheduledTask.execute(0.1, opts){ value }.tap{ sleep(0.2) }
35
+ end
36
+
37
+ def dereferenceable_observable(opts = {})
38
+ ScheduledTask.new(0.1, opts){ 'value' }
39
+ end
40
+
41
+ def execute_dereferenceable(subject)
42
+ subject.execute
43
+ sleep(0.2)
44
+ end
45
+
46
+ it_should_behave_like :dereferenceable
29
47
  end
30
48
 
31
49
  context '#initialize' do
32
50
 
33
- it 'accepts a number of seconds (from now) as the shcedule time' do
51
+ it 'accepts a number of seconds (from now) as the schedule time' do
34
52
  Timecop.freeze do
35
53
  now = Time.now
36
- task = ScheduledTask.new(60){ nil }
54
+ task = ScheduledTask.new(60){ nil }.execute
37
55
  task.schedule_time.to_i.should eq now.to_i + 60
38
56
  end
39
57
  end
40
58
 
41
59
  it 'accepts a time object as the schedule time' do
42
60
  schedule = Time.now + (60*10)
43
- task = ScheduledTask.new(schedule){ nil }
61
+ task = ScheduledTask.new(schedule){ nil }.execute
44
62
  task.schedule_time.should eq schedule
45
63
  end
46
64
 
47
65
  it 'raises an exception when seconds is less than zero' do
48
66
  expect {
49
67
  ScheduledTask.new(-1){ nil }
50
- }.to raise_error(ArgumentError)
68
+ }.to raise_error(ScheduledTask::SchedulingError)
51
69
  end
52
70
 
53
71
  it 'raises an exception when schedule time is in the past' do
54
72
  expect {
55
73
  ScheduledTask.new(Time.now - 60){ nil }
56
- }.to raise_error(ArgumentError)
74
+ }.to raise_error(ScheduledTask::SchedulingError)
57
75
  end
58
76
 
59
77
  it 'raises an exception when no block given' do
60
78
  expect {
61
79
  ScheduledTask.new(1)
62
- }.to raise_error(ArgumentError)
80
+ }.to raise_error(ScheduledTask::SchedulingError)
81
+ end
82
+
83
+ it 'sets the initial state to :unscheduled' do
84
+ task = ScheduledTask.new(1){ nil }
85
+ task.should be_unscheduled
86
+ end
87
+
88
+ it 'sets the #schedule_time to nil prior to execution' do
89
+ task = ScheduledTask.new(1){ nil }
90
+ task.schedule_time.should be_nil
91
+ end
92
+ end
93
+
94
+ context 'instance #execute' do
95
+
96
+ it 'does nothing unless the state is :unscheduled' do
97
+ Thread.should_not_receive(:new).with(any_args)
98
+ task = ScheduledTask.new(1){ nil }
99
+ task.instance_variable_set(:@state, :pending)
100
+ task.execute
101
+ task.instance_variable_set(:@state, :rejected)
102
+ task.execute
103
+ task.instance_variable_set(:@state, :fulfilled)
104
+ task.execute
105
+ end
106
+
107
+ it 'calculates the #schedule_time on execution' do
108
+ Timecop.freeze do
109
+ now = Time.now
110
+ task = ScheduledTask.new(5){ nil }
111
+ Timecop.travel(10)
112
+ task.execute
113
+ task.schedule_time.to_i.should eq now.to_i + 15
114
+ end
115
+ end
116
+
117
+ it 'raises an exception if expected schedule time is in the past' do
118
+ Timecop.freeze do
119
+ schedule = Time.now + (10)
120
+ task = ScheduledTask.new(schedule){ nil }
121
+ Timecop.travel(60)
122
+ expect {
123
+ task.execute
124
+ }.to raise_error(ScheduledTask::SchedulingError)
125
+ end
126
+ end
127
+
128
+ it 'spawns a new thread when a block was given on construction' do
129
+ Thread.should_receive(:new).with(any_args)
130
+ task = ScheduledTask.new(1){ nil }
131
+ task.execute
63
132
  end
64
133
 
65
- it 'sets the initial state to :pending' do
134
+ it 'sets the sate to :pending' do
66
135
  task = ScheduledTask.new(1){ nil }
136
+ task.execute
67
137
  task.should be_pending
68
138
  end
139
+
140
+ it 'returns self' do
141
+ task = ScheduledTask.new(1){ nil }
142
+ task.execute.should eq task
143
+ end
144
+ end
145
+
146
+ context 'class #execute' do
147
+
148
+ it 'creates a new ScheduledTask' do
149
+ task = ScheduledTask.execute(1){ nil }
150
+ task.should be_a(ScheduledTask)
151
+ end
152
+
153
+ it 'passes the block to the new ScheduledTask' do
154
+ @expected = false
155
+ task = ScheduledTask.execute(0.1){ @expected = true }
156
+ sleep(0.2)
157
+ @expected.should be_true
158
+ end
159
+
160
+ it 'calls #execute on the new ScheduledTask' do
161
+ task = ScheduledTask.new(0.1){ nil }
162
+ ScheduledTask.stub(:new).with(any_args).and_return(task)
163
+ task.should_receive(:execute).with(no_args)
164
+ ScheduledTask.execute(0.1){ nil }
165
+ end
69
166
  end
70
167
 
71
168
  context '#cancel' do
72
169
 
73
170
  it 'returns false if the task has already been performed' do
74
- task = ScheduledTask.new(0.1){ 42 }
171
+ task = ScheduledTask.new(0.1){ 42 }.execute
75
172
  sleep(0.2)
76
173
  task.cancel.should be_false
77
174
  end
78
175
 
79
176
  it 'returns false if the task is already in progress' do
80
- task = ScheduledTask.new(0.1){ sleep(1); 42 }
177
+ task = ScheduledTask.new(0.1){ sleep(1); 42 }.execute
81
178
  sleep(0.2)
82
179
  task.cancel.should be_false
83
180
  end
84
181
 
182
+ it 'cancels the task if it has not yet scheduled' do
183
+ @expected = true
184
+ task = ScheduledTask.new(0.1){ @expected = false }
185
+ task.cancel
186
+ task.execute
187
+ sleep(0.5)
188
+ @expected.should be_true
189
+ end
190
+
191
+
85
192
  it 'cancels the task if it has not yet started' do
86
193
  @expected = true
87
- task = ScheduledTask.new(0.3){ @expected = false }
194
+ task = ScheduledTask.new(0.3){ @expected = false }.execute
88
195
  sleep(0.1)
89
196
  task.cancel
90
197
  sleep(0.5)
@@ -92,13 +199,13 @@ module Concurrent
92
199
  end
93
200
 
94
201
  it 'returns true on success' do
95
- task = ScheduledTask.new(0.3){ @expected = false }
202
+ task = ScheduledTask.new(0.3){ @expected = false }.execute
96
203
  sleep(0.1)
97
204
  task.cancel.should be_true
98
205
  end
99
206
 
100
207
  it 'sets the state to :cancelled when cancelled' do
101
- task = ScheduledTask.new(10){ 42 }
208
+ task = ScheduledTask.new(10){ 42 }.execute
102
209
  sleep(0.1)
103
210
  task.cancel
104
211
  task.should be_cancelled
@@ -108,7 +215,7 @@ module Concurrent
108
215
  context 'execution' do
109
216
 
110
217
  it 'sets the state to :in_progress when the task is running' do
111
- task = ScheduledTask.new(0.1){ sleep(1); 42 }
218
+ task = ScheduledTask.new(0.1){ sleep(1); 42 }.execute
112
219
  sleep(0.2)
113
220
  task.should be_in_progress
114
221
  end
@@ -131,44 +238,42 @@ module Concurrent
131
238
 
132
239
  let(:observer) { clazz.new }
133
240
 
134
- it 'returns true for an observer added while :pending' do
135
- task = ScheduledTask.new(1){ 42 }
241
+ it 'returns true for an observer added while :unscheduled' do
242
+ task = ScheduledTask.new(0.1){ 42 }
136
243
  task.add_observer(observer).should be_true
137
244
  end
138
245
 
139
- it 'returns true for an observer added while :in_progress' do
140
- task = ScheduledTask.new(0.1){ sleep(1); 42 }
141
- sleep(0.2)
246
+ it 'returns true for an observer added while :pending' do
247
+ task = ScheduledTask.new(0.1){ 42 }.execute
142
248
  task.add_observer(observer).should be_true
143
249
  end
144
250
 
145
- it 'returns true for an observer added while not running' do
146
- task = ScheduledTask.new(1){ 42 }
251
+ it 'returns true for an observer added while :in_progress' do
252
+ task = ScheduledTask.new(0.1){ sleep(1); 42 }.execute
253
+ sleep(0.2)
147
254
  task.add_observer(observer).should be_true
148
255
  end
149
256
 
150
257
  it 'returns false for an observer added once :cancelled' do
151
258
  task = ScheduledTask.new(1){ 42 }
152
- sleep(0.1)
153
259
  task.cancel
154
- sleep(0.1)
155
260
  task.add_observer(observer).should be_false
156
261
  end
157
262
 
158
263
  it 'returns false for an observer added once :fulfilled' do
159
- task = ScheduledTask.new(0.1){ 42 }
264
+ task = ScheduledTask.new(0.1){ 42 }.execute
160
265
  sleep(0.2)
161
266
  task.add_observer(observer).should be_false
162
267
  end
163
268
 
164
269
  it 'returns false for an observer added once :rejected' do
165
- task = ScheduledTask.new(0.1){ raise StandardError }
270
+ task = ScheduledTask.new(0.1){ raise StandardError }.execute
166
271
  sleep(0.2)
167
272
  task.add_observer(observer).should be_false
168
273
  end
169
274
 
170
275
  it 'notifies all observers on fulfillment' do
171
- task = ScheduledTask.new(0.1){ 42 }
276
+ task = ScheduledTask.new(0.1){ 42 }.execute
172
277
  task.add_observer(observer)
173
278
  sleep(0.2)
174
279
  task.value.should == 42
@@ -178,7 +283,7 @@ module Concurrent
178
283
  end
179
284
 
180
285
  it 'notifies all observers on rejection' do
181
- task = ScheduledTask.new(0.1){ raise StandardError }
286
+ task = ScheduledTask.new(0.1){ raise StandardError }.execute
182
287
  task.add_observer(observer)
183
288
  sleep(0.2)
184
289
  task.value.should be_nil
@@ -188,30 +293,29 @@ module Concurrent
188
293
  end
189
294
 
190
295
  it 'does not notify an observer added after fulfillment' do
191
- observer.should_not_receive(:update).with(any_args())
192
- task = ScheduledTask.new(0.1){ 42 }
296
+ observer.should_not_receive(:update).with(any_args)
297
+ task = ScheduledTask.new(0.1){ 42 }.execute
193
298
  sleep(0.2)
194
299
  task.add_observer(observer)
195
300
  sleep(0.1)
196
301
  end
197
302
 
198
303
  it 'does not notify an observer added after rejection' do
199
- observer.should_not_receive(:update).with(any_args())
200
- task = ScheduledTask.new(0.1){ raise StandardError }
304
+ observer.should_not_receive(:update).with(any_args)
305
+ task = ScheduledTask.new(0.1){ raise StandardError }.execute
201
306
  sleep(0.2)
202
307
  task.add_observer(observer)
203
308
  sleep(0.1)
204
309
  end
205
310
 
206
311
  it 'does not notify an observer added after cancellation' do
207
- observer.should_not_receive(:update).with(any_args())
208
- task = ScheduledTask.new(0.5){ 42 }
209
- sleep(0.1)
312
+ observer.should_not_receive(:update).with(any_args)
313
+ task = ScheduledTask.new(0.1){ 42 }.execute
210
314
  task.cancel
211
- sleep(0.1)
212
315
  task.add_observer(observer)
213
- sleep(0.5)
316
+ sleep(0.2)
214
317
  end
318
+
215
319
  end
216
320
  end
217
321
  end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+ require 'rbconfig'
3
+
4
+ module Concurrent
5
+
6
+ describe ThreadLocalVar do
7
+
8
+ subject{ ThreadLocalVar.new }
9
+
10
+ context '#initialize' do
11
+
12
+ it 'can set an initial value' do
13
+ v = ThreadLocalVar.new(14)
14
+ v.value.should eq 14
15
+ end
16
+
17
+ it 'sets nil as a default initial value' do
18
+ v = ThreadLocalVar.new
19
+ v.value.should be_nil
20
+ end
21
+
22
+ it 'sets the same initial value for all threads' do
23
+ v = ThreadLocalVar.new(14)
24
+ t1 = Thread.new { v.value }
25
+ t2 = Thread.new { v.value }
26
+ t1.value.should eq 14
27
+ t2.value.should eq 14
28
+ end
29
+
30
+ if jruby?
31
+ it 'uses ThreadLocalJavaStorage' do
32
+ subject.class.ancestors.should include(Concurrent::ThreadLocalJavaStorage)
33
+ end
34
+ elsif rbx? || RbConfig::CONFIG['ruby_version'] =~ /^1\.9/
35
+ it 'uses ThreadLocalOldStorage' do
36
+ subject.class.ancestors.should include(Concurrent::ThreadLocalOldStorage)
37
+ end
38
+ else
39
+ it 'uses ThreadLocalNewStorage' do
40
+ subject.class.ancestors.should include(Concurrent::ThreadLocalNewStorage)
41
+ end
42
+ end
43
+ end
44
+
45
+ context '#value' do
46
+
47
+ it 'returns the current value' do
48
+ v = ThreadLocalVar.new(14)
49
+ v.value.should eq 14
50
+ end
51
+
52
+ it 'returns the value after modification' do
53
+ v = ThreadLocalVar.new(14)
54
+ v.value = 2
55
+ v.value.should eq 2
56
+ end
57
+
58
+ end
59
+
60
+ context '#value=' do
61
+
62
+ it 'sets a new value' do
63
+ v = ThreadLocalVar.new(14)
64
+ v.value = 2
65
+ v.value.should eq 2
66
+ end
67
+
68
+ it 'returns the new value' do
69
+ v = ThreadLocalVar.new(14)
70
+ (v.value = 2).should eq 2
71
+ end
72
+
73
+ it 'does not modify the initial value for other threads' do
74
+ v = ThreadLocalVar.new(14)
75
+ v.value = 2
76
+ t = Thread.new { v.value }
77
+ t.value.should eq 14
78
+ end
79
+
80
+ it 'does not modify the value for other threads' do
81
+ v = ThreadLocalVar.new(14)
82
+ v.value = 2
83
+
84
+ b1 = CountDownLatch.new(2)
85
+ b2 = CountDownLatch.new(2)
86
+
87
+ t1 = Thread.new do
88
+ b1.count_down
89
+ b1.wait
90
+ v.value = 1
91
+ b2.count_down
92
+ b2.wait
93
+ v.value
94
+ end
95
+
96
+ t2 = Thread.new do
97
+ b1.count_down
98
+ b1.wait
99
+ v.value = 2
100
+ b2.count_down
101
+ b2.wait
102
+ v.value
103
+ end
104
+
105
+ t1.value.should eq 1
106
+ t2.value.should eq 2
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+
113
+ end