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