concurrent-ruby 0.3.0 → 0.3.1.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.
- checksums.yaml +4 -4
- data/README.md +55 -5
- data/lib/concurrent.rb +3 -0
- data/lib/concurrent/actor.rb +90 -36
- data/lib/concurrent/agent.rb +7 -21
- data/lib/concurrent/contract.rb +20 -0
- data/lib/concurrent/dereferenceable.rb +33 -0
- data/lib/concurrent/future.rb +7 -6
- data/lib/concurrent/obligation.rb +4 -3
- data/lib/concurrent/promise.rb +3 -3
- data/lib/concurrent/scheduled_task.rb +94 -0
- data/lib/concurrent/version.rb +1 -1
- data/md/actor.md +209 -0
- data/md/agent.md +30 -11
- data/md/future.md +55 -13
- data/md/scheduled_task.md +34 -0
- data/md/supervisor.md +209 -6
- data/md/timer_task.md +1 -1
- data/spec/concurrent/actor_spec.rb +244 -48
- data/spec/concurrent/agent_spec.rb +52 -1
- data/spec/concurrent/contract_spec.rb +34 -0
- data/spec/concurrent/future_spec.rb +6 -1
- data/spec/concurrent/obligation_shared.rb +1 -1
- data/spec/concurrent/promise_spec.rb +6 -1
- data/spec/concurrent/runnable_shared.rb +1 -1
- data/spec/concurrent/scheduled_task_spec.rb +259 -0
- metadata +14 -5
@@ -23,6 +23,11 @@ module Concurrent
|
|
23
23
|
Agent.thread_pool = FixedThreadPool.new(1)
|
24
24
|
end
|
25
25
|
|
26
|
+
it 'includes Dereferenceable' do
|
27
|
+
agent = Agent.new(10)
|
28
|
+
agent.should be_a(Dereferenceable)
|
29
|
+
end
|
30
|
+
|
26
31
|
context '#initialize' do
|
27
32
|
|
28
33
|
it 'sets the value to the given initial state' do
|
@@ -268,7 +273,7 @@ module Concurrent
|
|
268
273
|
end
|
269
274
|
end
|
270
275
|
|
271
|
-
context '
|
276
|
+
context 'Dereferenceable' do
|
272
277
|
|
273
278
|
it 'defaults :dup_on_deref to false' do
|
274
279
|
value = 'value'
|
@@ -362,6 +367,52 @@ module Concurrent
|
|
362
367
|
agent = Agent.new(value, dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copy)
|
363
368
|
agent.value.should eq frozen
|
364
369
|
end
|
370
|
+
|
371
|
+
it 'does not call #dup when #dup_on_deref is set and the value is nil' do
|
372
|
+
allow_message_expectations_on_nil
|
373
|
+
result = nil
|
374
|
+
result.should_not_receive(:dup).with(any_args())
|
375
|
+
agent = Agent.new(result, dup_on_deref: true)
|
376
|
+
agent.value
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'does not call #freeze when #freeze_on_deref is set and the value is nil' do
|
380
|
+
allow_message_expectations_on_nil
|
381
|
+
result = nil
|
382
|
+
result.should_not_receive(:freeze).with(any_args())
|
383
|
+
agent = Agent.new(result, freeze_on_deref: true)
|
384
|
+
agent.value
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'does not call the #copy_on_deref block when the value is nil' do
|
388
|
+
copier = proc { 42 }
|
389
|
+
agent = Agent.new(nil, copy_on_deref: copier)
|
390
|
+
agent.value.should be_nil
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'does not lock when all options are false' do
|
394
|
+
agent = Agent.new(0)
|
395
|
+
mutex = double('mutex')
|
396
|
+
agent.stub(:mutex).and_return(mutex)
|
397
|
+
mutex.should_not_receive(:synchronize)
|
398
|
+
agent.value
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'supports dereference flags with observers' do
|
402
|
+
result = 'result'
|
403
|
+
result.should_receive(:dup).and_return(result)
|
404
|
+
result.should_receive(:freeze).and_return(result)
|
405
|
+
copier = proc { result }
|
406
|
+
|
407
|
+
observer = double('observer')
|
408
|
+
observer.should_receive(:update).with(any_args())
|
409
|
+
|
410
|
+
agent = Agent.new(nil, dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copier)
|
411
|
+
agent.add_observer(observer)
|
412
|
+
|
413
|
+
agent << proc { 'original result' }
|
414
|
+
sleep(0.1)
|
415
|
+
end
|
365
416
|
end
|
366
417
|
|
367
418
|
context 'observation' do
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'obligation_shared'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
describe Contract do
|
7
|
+
|
8
|
+
let!(:fulfilled_value) { 10 }
|
9
|
+
let(:rejected_reason) { StandardError.new('Boom!') }
|
10
|
+
|
11
|
+
let(:pending_subject) do
|
12
|
+
@contract = Contract.new
|
13
|
+
Thread.new do
|
14
|
+
sleep(3)
|
15
|
+
@contract.complete(fulfilled_value, nil)
|
16
|
+
end
|
17
|
+
@contract
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:fulfilled_subject) do
|
21
|
+
contract = Contract.new
|
22
|
+
contract.complete(fulfilled_value, nil)
|
23
|
+
contract
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:rejected_subject) do
|
27
|
+
contract = Contract.new
|
28
|
+
contract.complete(nil, rejected_reason)
|
29
|
+
contract
|
30
|
+
end
|
31
|
+
|
32
|
+
it_should_behave_like :obligation
|
33
|
+
end
|
34
|
+
end
|
@@ -28,7 +28,12 @@ module Concurrent
|
|
28
28
|
Future.thread_pool = FixedThreadPool.new(1)
|
29
29
|
end
|
30
30
|
|
31
|
-
it_should_behave_like
|
31
|
+
it_should_behave_like :obligation
|
32
|
+
|
33
|
+
it 'includes Dereferenceable' do
|
34
|
+
future = Future.new{ nil }
|
35
|
+
future.should be_a(Dereferenceable)
|
36
|
+
end
|
32
37
|
|
33
38
|
context '#initialize' do
|
34
39
|
|
@@ -29,7 +29,12 @@ module Concurrent
|
|
29
29
|
Promise.thread_pool = FixedThreadPool.new(1)
|
30
30
|
end
|
31
31
|
|
32
|
-
it_should_behave_like
|
32
|
+
it_should_behave_like :obligation
|
33
|
+
|
34
|
+
it 'includes Dereferenceable' do
|
35
|
+
promise = Promise.new{ nil }
|
36
|
+
promise.should be_a(Dereferenceable)
|
37
|
+
end
|
33
38
|
|
34
39
|
context '#then' do
|
35
40
|
|
@@ -12,7 +12,7 @@ share_examples_for :runnable do
|
|
12
12
|
|
13
13
|
it 'starts the (blocking) runner on the current thread when stopped' do
|
14
14
|
@thread = Thread.new { subject.run }
|
15
|
-
@thread.join(1).should be_nil
|
15
|
+
@thread.join(0.1).should be_nil
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'raises an exception when already running' do
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timecop'
|
3
|
+
require_relative 'obligation_shared'
|
4
|
+
require_relative 'runnable_shared'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
describe ScheduledTask do
|
9
|
+
|
10
|
+
context 'behavior' do
|
11
|
+
|
12
|
+
# runnable
|
13
|
+
|
14
|
+
subject { ScheduledTask.new(0.5){ nil } }
|
15
|
+
it_should_behave_like :runnable
|
16
|
+
|
17
|
+
# obligation
|
18
|
+
|
19
|
+
let!(:fulfilled_value) { 10 }
|
20
|
+
let!(:rejected_reason) { StandardError.new('mojo jojo') }
|
21
|
+
|
22
|
+
let(:pending_subject) do
|
23
|
+
task = ScheduledTask.new(1){ fulfilled_value }
|
24
|
+
task.run!
|
25
|
+
task
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:fulfilled_subject) do
|
29
|
+
task = ScheduledTask.new(0.1){ fulfilled_value }
|
30
|
+
task.run
|
31
|
+
task
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:rejected_subject) do
|
35
|
+
task = ScheduledTask.new(0.1){ raise rejected_reason }
|
36
|
+
task.run
|
37
|
+
task
|
38
|
+
end
|
39
|
+
|
40
|
+
it_should_behave_like :obligation
|
41
|
+
end
|
42
|
+
|
43
|
+
context '#initialize' do
|
44
|
+
|
45
|
+
it 'accepts a number of seconds (from now) as the shcedule time' do
|
46
|
+
Timecop.freeze do
|
47
|
+
now = Time.now
|
48
|
+
task = ScheduledTask.new(60){ nil }
|
49
|
+
task.schedule_time.to_i.should eq now.to_i + 60
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'accepts a time object as the schedule time' do
|
54
|
+
schedule = Time.now + (60*10)
|
55
|
+
task = ScheduledTask.new(schedule){ nil }
|
56
|
+
task.schedule_time.should eq schedule
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises an exception when seconds is less than zero' do
|
60
|
+
expect {
|
61
|
+
ScheduledTask.new(-1){ nil }
|
62
|
+
}.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'raises an exception when schedule time is in the past' do
|
66
|
+
expect {
|
67
|
+
ScheduledTask.new(Time.now - 60){ nil }
|
68
|
+
}.to raise_error(ArgumentError)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'raises an exception when no block given' do
|
72
|
+
expect {
|
73
|
+
ScheduledTask.new(1)
|
74
|
+
}.to raise_error(ArgumentError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'sets the initial state to :pending' do
|
78
|
+
task = ScheduledTask.new(1){ nil }
|
79
|
+
task.should be_pending
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context '#cancel' do
|
84
|
+
|
85
|
+
it 'returns false if the task has already been performed' do
|
86
|
+
task = ScheduledTask.new(0.1){ 42 }
|
87
|
+
task.run!
|
88
|
+
sleep(0.2)
|
89
|
+
task.cancel.should be_false
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns false if the task is already in progress' do
|
93
|
+
task = ScheduledTask.new(0.1){ sleep(1); 42 }
|
94
|
+
task.run!
|
95
|
+
sleep(0.2)
|
96
|
+
task.cancel.should be_false
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'cancels the task if it has not yet started' do
|
100
|
+
@expected = true
|
101
|
+
task = ScheduledTask.new(0.3){ @expected = false }
|
102
|
+
task.run!
|
103
|
+
sleep(0.1)
|
104
|
+
task.cancel
|
105
|
+
sleep(0.5)
|
106
|
+
@expected.should be_true
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns true on success' do
|
110
|
+
task = ScheduledTask.new(0.3){ @expected = false }
|
111
|
+
task.run!
|
112
|
+
sleep(0.1)
|
113
|
+
task.cancel.should be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'sets the state to :cancelled when cancelled' do
|
117
|
+
task = ScheduledTask.new(10){ 42 }
|
118
|
+
task.run!
|
119
|
+
sleep(0.1)
|
120
|
+
task.cancel
|
121
|
+
task.should be_cancelled
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'stops the runnable' do
|
125
|
+
task = ScheduledTask.new(0.2){ 42 }
|
126
|
+
task.run!
|
127
|
+
sleep(0.1)
|
128
|
+
task.cancel
|
129
|
+
sleep(0.2)
|
130
|
+
task.should_not be_running
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'execution' do
|
135
|
+
|
136
|
+
it 'sets the state to :in_progress when the task is running' do
|
137
|
+
task = ScheduledTask.new(0.1){ sleep(1); 42 }
|
138
|
+
task.run!
|
139
|
+
sleep(0.2)
|
140
|
+
task.should be_in_progress
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'stops itself on task completion' do
|
144
|
+
task = ScheduledTask.new(0.1){ 42 }
|
145
|
+
task.run!
|
146
|
+
sleep(0.2)
|
147
|
+
task.should_not be_running
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'observation' do
|
152
|
+
|
153
|
+
let(:clazz) do
|
154
|
+
Class.new do
|
155
|
+
attr_reader :value
|
156
|
+
attr_reader :reason
|
157
|
+
attr_reader :count
|
158
|
+
define_method(:update) do |time, value, reason|
|
159
|
+
@count = @count.to_i + 1
|
160
|
+
@value = value
|
161
|
+
@reason = reason
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
let(:observer) { clazz.new }
|
167
|
+
|
168
|
+
it 'returns true for an observer added while :pending' do
|
169
|
+
task = ScheduledTask.new(1){ 42 }
|
170
|
+
task.run!
|
171
|
+
task.add_observer(observer).should be_true
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'returns true for an observer added while :in_progress' do
|
175
|
+
task = ScheduledTask.new(0.1){ sleep(1); 42 }
|
176
|
+
task.run!
|
177
|
+
sleep(0.2)
|
178
|
+
task.add_observer(observer).should be_true
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'returns true for an observer added while not running' do
|
182
|
+
task = ScheduledTask.new(1){ 42 }
|
183
|
+
task.add_observer(observer).should be_true
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'returns false for an observer added once :cancelled' do
|
187
|
+
task = ScheduledTask.new(1){ 42 }
|
188
|
+
task.run!
|
189
|
+
sleep(0.1)
|
190
|
+
task.cancel
|
191
|
+
sleep(0.1)
|
192
|
+
task.add_observer(observer).should be_false
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'returns false for an observer added once :fulfilled' do
|
196
|
+
task = ScheduledTask.new(0.1){ 42 }
|
197
|
+
task.run!
|
198
|
+
sleep(0.2)
|
199
|
+
task.add_observer(observer).should be_false
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'returns false for an observer added once :rejected' do
|
203
|
+
task = ScheduledTask.new(0.1){ raise StandardError }
|
204
|
+
task.run!
|
205
|
+
sleep(0.2)
|
206
|
+
task.add_observer(observer).should be_false
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'notifies all observers on fulfillment' do
|
210
|
+
task = ScheduledTask.new(0.1){ 42 }
|
211
|
+
task.add_observer(observer)
|
212
|
+
task.run!
|
213
|
+
sleep(0.2)
|
214
|
+
task.value.should == 42
|
215
|
+
task.reason.should be_nil
|
216
|
+
observer.value.should == 42
|
217
|
+
observer.reason.should be_nil
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'notifies all observers on rejection' do
|
221
|
+
task = ScheduledTask.new(0.1){ raise StandardError }
|
222
|
+
task.add_observer(observer)
|
223
|
+
task.run!
|
224
|
+
sleep(0.2)
|
225
|
+
task.value.should be_nil
|
226
|
+
task.reason.should be_a(StandardError)
|
227
|
+
observer.value.should be_nil
|
228
|
+
observer.reason.should be_a(StandardError)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'does not notify an observer added after fulfillment' do
|
232
|
+
observer.should_not_receive(:update).with(any_args())
|
233
|
+
task = ScheduledTask.new(0.1){ 42 }
|
234
|
+
sleep(0.2)
|
235
|
+
task.add_observer(observer)
|
236
|
+
sleep(0.1)
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'does not notify an observer added after rejection' do
|
240
|
+
observer.should_not_receive(:update).with(any_args())
|
241
|
+
task = ScheduledTask.new(0.1){ raise StandardError }
|
242
|
+
sleep(0.2)
|
243
|
+
task.add_observer(observer)
|
244
|
+
sleep(0.1)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'does not notify an observer added after cancellation' do
|
248
|
+
observer.should_not_receive(:update).with(any_args())
|
249
|
+
task = ScheduledTask.new(0.5){ 42 }
|
250
|
+
task.run!
|
251
|
+
sleep(0.1)
|
252
|
+
task.cancel
|
253
|
+
sleep(0.1)
|
254
|
+
task.add_observer(observer)
|
255
|
+
sleep(0.5)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jerry D'Antonio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -40,6 +40,8 @@ files:
|
|
40
40
|
- lib/concurrent/agent.rb
|
41
41
|
- lib/concurrent/cached_thread_pool/worker.rb
|
42
42
|
- lib/concurrent/cached_thread_pool.rb
|
43
|
+
- lib/concurrent/contract.rb
|
44
|
+
- lib/concurrent/dereferenceable.rb
|
43
45
|
- lib/concurrent/event.rb
|
44
46
|
- lib/concurrent/event_machine_defer_proxy.rb
|
45
47
|
- lib/concurrent/fixed_thread_pool/worker.rb
|
@@ -50,24 +52,28 @@ files:
|
|
50
52
|
- lib/concurrent/obligation.rb
|
51
53
|
- lib/concurrent/promise.rb
|
52
54
|
- lib/concurrent/runnable.rb
|
55
|
+
- lib/concurrent/scheduled_task.rb
|
53
56
|
- lib/concurrent/supervisor.rb
|
54
57
|
- lib/concurrent/timer_task.rb
|
55
58
|
- lib/concurrent/utilities.rb
|
56
59
|
- lib/concurrent/version.rb
|
57
60
|
- lib/concurrent.rb
|
58
61
|
- lib/concurrent_ruby.rb
|
62
|
+
- md/actor.md
|
59
63
|
- md/agent.md
|
60
64
|
- md/event.md
|
61
65
|
- md/future.md
|
62
66
|
- md/goroutine.md
|
63
67
|
- md/obligation.md
|
64
68
|
- md/promise.md
|
69
|
+
- md/scheduled_task.md
|
65
70
|
- md/supervisor.md
|
66
71
|
- md/thread_pool.md
|
67
72
|
- md/timer_task.md
|
68
73
|
- spec/concurrent/actor_spec.rb
|
69
74
|
- spec/concurrent/agent_spec.rb
|
70
75
|
- spec/concurrent/cached_thread_pool_spec.rb
|
76
|
+
- spec/concurrent/contract_spec.rb
|
71
77
|
- spec/concurrent/event_machine_defer_proxy_spec.rb
|
72
78
|
- spec/concurrent/event_spec.rb
|
73
79
|
- spec/concurrent/fixed_thread_pool_spec.rb
|
@@ -78,6 +84,7 @@ files:
|
|
78
84
|
- spec/concurrent/promise_spec.rb
|
79
85
|
- spec/concurrent/runnable_shared.rb
|
80
86
|
- spec/concurrent/runnable_spec.rb
|
87
|
+
- spec/concurrent/scheduled_task_spec.rb
|
81
88
|
- spec/concurrent/supervisor_spec.rb
|
82
89
|
- spec/concurrent/thread_pool_shared.rb
|
83
90
|
- spec/concurrent/timer_task_spec.rb
|
@@ -103,12 +110,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
110
|
version: 1.9.2
|
104
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
112
|
requirements:
|
106
|
-
- - '
|
113
|
+
- - '>'
|
107
114
|
- !ruby/object:Gem::Version
|
108
|
-
version:
|
115
|
+
version: 1.3.1
|
109
116
|
requirements: []
|
110
117
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.1.
|
118
|
+
rubygems_version: 2.1.10
|
112
119
|
signing_key:
|
113
120
|
specification_version: 4
|
114
121
|
summary: Modern concurrency tools including agents, futures, promises, thread pools,
|
@@ -117,6 +124,7 @@ test_files:
|
|
117
124
|
- spec/concurrent/actor_spec.rb
|
118
125
|
- spec/concurrent/agent_spec.rb
|
119
126
|
- spec/concurrent/cached_thread_pool_spec.rb
|
127
|
+
- spec/concurrent/contract_spec.rb
|
120
128
|
- spec/concurrent/event_machine_defer_proxy_spec.rb
|
121
129
|
- spec/concurrent/event_spec.rb
|
122
130
|
- spec/concurrent/fixed_thread_pool_spec.rb
|
@@ -127,6 +135,7 @@ test_files:
|
|
127
135
|
- spec/concurrent/promise_spec.rb
|
128
136
|
- spec/concurrent/runnable_shared.rb
|
129
137
|
- spec/concurrent/runnable_spec.rb
|
138
|
+
- spec/concurrent/scheduled_task_spec.rb
|
130
139
|
- spec/concurrent/supervisor_spec.rb
|
131
140
|
- spec/concurrent/thread_pool_shared.rb
|
132
141
|
- spec/concurrent/timer_task_spec.rb
|