concurrent-ruby 0.3.1.pre.1 → 0.3.1.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +37 -32
- data/lib/concurrent/scheduled_task.rb +7 -6
- data/lib/concurrent/timer_task.rb +11 -8
- data/lib/concurrent/version.rb +1 -1
- data/md/timer_task.md +22 -29
- data/spec/concurrent/scheduled_task_spec.rb +3 -45
- data/spec/concurrent/timer_task_spec.rb +29 -19
- metadata +19 -16
- checksums.yaml +0 -7
data/README.md
CHANGED
@@ -6,32 +6,35 @@ Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency pat
|
|
6
6
|
If you find this gem useful you should check out my [functional-ruby](https://github.com/jdantonio/functional-ruby)
|
7
7
|
gem, too. This gem uses several of the tools in that gem.
|
8
8
|
|
9
|
-
## Conference Presentations
|
10
|
-
|
11
|
-
I've given several conference presentations on concurrent programming with this gem.
|
12
|
-
Check them out:
|
13
|
-
|
14
|
-
* ["Advanced Concurrent Programming in Ruby"](http://rubyconf.org/program#jerry-dantonio)
|
15
|
-
at [RubyConf 2013](http://rubyconf.org/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation) version of the presentation
|
16
|
-
* ["Advanced Multithreading in Ruby"](http://cascadiaruby.com/#advanced-multithreading-in-ruby)
|
17
|
-
at [Cascadia Ruby 2013](http://cascadiaruby.com/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation/tree/cascadia-ruby-2013) version of the presentation
|
18
|
-
* I'll be giving ["Advanced Concurrent Programming in Ruby"](http://codemash.org/sessions)
|
19
|
-
at [CodeMash 2014](http://codemash.org/)
|
20
|
-
|
21
9
|
## Introduction
|
22
10
|
|
23
|
-
The old-school "lock and synchronize" approach to concurrency is dead. The
|
24
|
-
is asynchronous. Send out a bunch of independent
|
25
|
-
to do your bidding and
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
11
|
+
The old-school "lock and synchronize" approach to concurrency is dead. The
|
12
|
+
future of concurrency is asynchronous. Send out a bunch of independent
|
13
|
+
[actors](http://en.wikipedia.org/wiki/Actor_model) to do your bidding and
|
14
|
+
process the results when you are ready. Many modern programming languages (like
|
15
|
+
[Erlang](http://www.erlang.org/doc/reference_manual/processes.html),
|
16
|
+
[Clojure](http://clojure.org/concurrent_programming),
|
17
|
+
[Scala](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor),
|
18
|
+
[Haskell](http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrent_Haskell),
|
19
|
+
[F#](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx),
|
20
|
+
[C#](http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx),
|
21
|
+
[Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html)...)
|
22
|
+
provide asynchronous concurrency mechanisms within their standard libraries, the
|
23
|
+
runtime environment, or the language iteself. This library implements a few of
|
24
|
+
the most interesting and useful of those variations.
|
25
|
+
|
26
|
+
Remember, *there is no silver bullet in concurrent programming.* Concurrency is hard.
|
32
27
|
These tools will help ease the burden, but at the end of the day it is essential that you
|
33
28
|
*know what you are doing.*
|
34
29
|
|
30
|
+
* Decouple business logic from concurrency logic
|
31
|
+
* Test business logic separate from concurrency logic
|
32
|
+
* Keep the intersection of business logic and concurrency and small as possible
|
33
|
+
* Don't share mutable data unless absolutely necessary
|
34
|
+
* Protect shared data as much as possible (prefer [immutability](https://github.com/harukizaemon/hamster))
|
35
|
+
* Don't mix Ruby's [concurrency](http://ruby-doc.org/core-2.0.0/Thread.html)
|
36
|
+
[primitives](http://www.ruby-doc.org/core-2.0.0/Mutex.html) with asynchronous concurrency libraries
|
37
|
+
|
35
38
|
The project is hosted on the following sites:
|
36
39
|
|
37
40
|
* [RubyGems project page](https://rubygems.org/gems/concurrent-ruby)
|
@@ -41,17 +44,19 @@ The project is hosted on the following sites:
|
|
41
44
|
* [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/concurrent-ruby)
|
42
45
|
* [Follow me on Twitter](https://twitter.com/jerrydantonio)
|
43
46
|
|
44
|
-
###
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
[
|
50
|
-
[
|
51
|
-
|
52
|
-
[
|
53
|
-
|
54
|
-
|
47
|
+
### Conference Presentations
|
48
|
+
|
49
|
+
I've given several conference presentations on concurrent programming with this gem.
|
50
|
+
Check them out:
|
51
|
+
|
52
|
+
* ["Advanced Concurrent Programming in Ruby"](http://rubyconf.org/program#jerry-dantonio)
|
53
|
+
at [RubyConf 2013](http://rubyconf.org/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation) version of the presentation
|
54
|
+
* ["Advanced Multithreading in Ruby"](http://cascadiaruby.com/#advanced-multithreading-in-ruby)
|
55
|
+
at [Cascadia Ruby 2013](http://cascadiaruby.com/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation/tree/cascadia-ruby-2013) version of the presentation
|
56
|
+
* I'll be giving ["Advanced Concurrent Programming in Ruby"](http://codemash.org/sessions)
|
57
|
+
at [CodeMash 2014](http://codemash.org/)
|
58
|
+
|
59
|
+
## Goals
|
55
60
|
|
56
61
|
* Stay true to the spirit of the languages providing inspiration
|
57
62
|
* But implement in a way that makes sense for Ruby
|
@@ -1,14 +1,11 @@
|
|
1
1
|
require 'observer'
|
2
|
-
|
3
2
|
require 'concurrent/obligation'
|
4
|
-
require 'concurrent/runnable'
|
5
3
|
|
6
4
|
module Concurrent
|
7
5
|
|
8
6
|
class ScheduledTask
|
9
7
|
include Obligation
|
10
8
|
include Observable
|
11
|
-
include Runnable
|
12
9
|
|
13
10
|
attr_reader :schedule_time
|
14
11
|
|
@@ -30,9 +27,12 @@ module Concurrent
|
|
30
27
|
end
|
31
28
|
|
32
29
|
@state = :pending
|
33
|
-
@task = block
|
34
30
|
@schedule_time.freeze
|
31
|
+
@task = block
|
35
32
|
set_deref_options(opts)
|
33
|
+
|
34
|
+
@thread = Thread.new{ work }
|
35
|
+
@thread.abort_on_exception = false
|
36
36
|
end
|
37
37
|
|
38
38
|
def cancelled?
|
@@ -55,15 +55,16 @@ module Concurrent
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
58
|
+
alias_method :stop, :cancel
|
58
59
|
|
59
60
|
def add_observer(observer, func = :update)
|
60
|
-
return false unless
|
61
|
+
return false unless [:pending, :in_progress].include?(@state)
|
61
62
|
super
|
62
63
|
end
|
63
64
|
|
64
65
|
protected
|
65
66
|
|
66
|
-
def
|
67
|
+
def work
|
67
68
|
while (diff = @schedule_time.to_f - Time.now.to_f) > 0
|
68
69
|
sleep( diff > 60 ? 60 : diff )
|
69
70
|
end
|
@@ -1,20 +1,22 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'observer'
|
3
3
|
|
4
|
+
require 'concurrent/dereferenceable'
|
4
5
|
require 'concurrent/runnable'
|
5
6
|
require 'concurrent/utilities'
|
6
7
|
|
7
8
|
module Concurrent
|
8
9
|
|
9
10
|
class TimerTask
|
11
|
+
include Dereferenceable
|
10
12
|
include Runnable
|
11
13
|
include Observable
|
12
14
|
|
13
15
|
EXECUTION_INTERVAL = 60
|
14
16
|
TIMEOUT_INTERVAL = 30
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
+
attr_accessor :execution_interval
|
19
|
+
attr_accessor :timeout_interval
|
18
20
|
|
19
21
|
def initialize(opts = {}, &block)
|
20
22
|
raise ArgumentError.new('no block given') unless block_given?
|
@@ -22,9 +24,9 @@ module Concurrent
|
|
22
24
|
@execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
|
23
25
|
@timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
|
24
26
|
@run_now = opts[:now] || opts[:run_now] || false
|
25
|
-
@block_args = opts[:args] || opts [:arguments] || []
|
26
27
|
|
27
28
|
@task = block
|
29
|
+
set_deref_options(opts)
|
28
30
|
end
|
29
31
|
|
30
32
|
def kill
|
@@ -65,17 +67,18 @@ module Concurrent
|
|
65
67
|
end
|
66
68
|
|
67
69
|
def execute_task
|
70
|
+
@value = ex = nil
|
68
71
|
@worker = Thread.new do
|
69
72
|
Thread.current.abort_on_exception = false
|
70
|
-
Thread.current[:result] = @task.call(
|
73
|
+
Thread.current[:result] = @task.call(self)
|
71
74
|
end
|
72
75
|
raise TimeoutError if @worker.join(@timeout_interval).nil?
|
73
|
-
|
74
|
-
notify_observers(Time.now, @worker[:result], nil)
|
76
|
+
mutex.synchronize { @value = @worker[:result] }
|
75
77
|
rescue Exception => ex
|
76
|
-
|
77
|
-
notify_observers(Time.now, nil, ex)
|
78
|
+
# suppress
|
78
79
|
ensure
|
80
|
+
changed
|
81
|
+
notify_observers(Time.now, self.value, ex)
|
79
82
|
unless @worker.nil?
|
80
83
|
Thread.kill(@worker)
|
81
84
|
@worker = nil
|
data/lib/concurrent/version.rb
CHANGED
data/md/timer_task.md
CHANGED
@@ -1,36 +1,29 @@
|
|
1
|
-
# To Gobbler's Knob. It's Groundhog Day
|
1
|
+
# To Gobbler's Knob. It's Groundhog Day!
|
2
2
|
|
3
3
|
A very common currency pattern is to run a thread that performs a task at regular
|
4
4
|
intervals. The thread that peforms the task sleeps for the given interval then
|
5
|
-
|
5
|
+
wakes up and performs the task. Lather, rinse, repeat... This pattern causes two
|
6
6
|
problems. First, it is difficult to test the business logic of the task becuse the
|
7
|
-
task itself is tightly
|
8
|
-
can cause the entire thread to abend. In a
|
9
|
-
thread is intended to run for days/weeks/years
|
10
|
-
problem.
|
11
|
-
|
12
|
-
When a TimerTask is launched it starts a thread for monitoring the execution interval.
|
13
|
-
The TimerTask thread does not perform the task, however. Instead, the TimerTask
|
14
|
-
launches the task on a
|
15
|
-
the task
|
16
|
-
TimerTask thread can
|
17
|
-
|
18
|
-
to
|
19
|
-
|
20
|
-
One other advantage of
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
continuously, the `TimerTask` is intended to manage fairly short operations that
|
28
|
-
occur repeatedly at regular intervals.
|
29
|
-
|
30
|
-
Unlike some of the others concurrency objects in the library, TimerTasks do not
|
31
|
-
run on the global thread pool. In my experience the types of tasks that will benefit
|
32
|
-
from the `TimerTask` class tend to also be long running. For this reason they get
|
33
|
-
their own thread every time the task is executed.
|
7
|
+
task itself is tightly coupled with the concurrency logic. Second, an exception in
|
8
|
+
raised while performing the task can cause the entire thread to abend. In a
|
9
|
+
long-running application where the task thread is intended to run for days/weeks/years
|
10
|
+
a crashed task thread can pose a significant problem. `TimerTask` alleviates both problems.
|
11
|
+
|
12
|
+
When a `TimerTask` is launched it starts a thread for monitoring the execution interval.
|
13
|
+
The `TimerTask` thread does not perform the task, however. Instead, the TimerTask
|
14
|
+
launches the task on a separate thread. Should the task experience an unrecoverable
|
15
|
+
crash only the task thread will crash. This makes the `TimerTask` very fault tolerant
|
16
|
+
Additionally, the `TimerTask` thread can respond to the success or failure of the task,
|
17
|
+
performing logging or ancillary operations. `TimerTask` can also be configured with a
|
18
|
+
timeout value allowing it to kill a task that runs too long.
|
19
|
+
|
20
|
+
One other advantage of `TimerTask` is it forces the bsiness logic to be completely decoupled
|
21
|
+
from the concurrency logic. The business logic can be tested separately then passed to the
|
22
|
+
`TimerTask` for scheduling and running.
|
23
|
+
|
24
|
+
Unlike other abstraction in this library, `TimerTask` does not run on the global thread pool.
|
25
|
+
In my experience the types of tasks that will benefit from `TimerTask` tend to also be long
|
26
|
+
running. For this reason they get their own thread every time the task is executed.
|
34
27
|
|
35
28
|
## Observation
|
36
29
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'timecop'
|
3
3
|
require_relative 'obligation_shared'
|
4
|
-
require_relative 'runnable_shared'
|
5
4
|
|
6
5
|
module Concurrent
|
7
6
|
|
@@ -9,32 +8,21 @@ module Concurrent
|
|
9
8
|
|
10
9
|
context 'behavior' do
|
11
10
|
|
12
|
-
# runnable
|
13
|
-
|
14
|
-
subject { ScheduledTask.new(0.5){ nil } }
|
15
|
-
it_should_behave_like :runnable
|
16
|
-
|
17
11
|
# obligation
|
18
12
|
|
19
13
|
let!(:fulfilled_value) { 10 }
|
20
14
|
let!(:rejected_reason) { StandardError.new('mojo jojo') }
|
21
15
|
|
22
16
|
let(:pending_subject) do
|
23
|
-
|
24
|
-
task.run!
|
25
|
-
task
|
17
|
+
ScheduledTask.new(1){ fulfilled_value }
|
26
18
|
end
|
27
19
|
|
28
20
|
let(:fulfilled_subject) do
|
29
|
-
|
30
|
-
task.run
|
31
|
-
task
|
21
|
+
ScheduledTask.new(0.1){ fulfilled_value }.tap(){ sleep(0.2) }
|
32
22
|
end
|
33
23
|
|
34
24
|
let(:rejected_subject) do
|
35
|
-
|
36
|
-
task.run
|
37
|
-
task
|
25
|
+
ScheduledTask.new(0.1){ raise rejected_reason }.tap(){ sleep(0.2) }
|
38
26
|
end
|
39
27
|
|
40
28
|
it_should_behave_like :obligation
|
@@ -84,14 +72,12 @@ module Concurrent
|
|
84
72
|
|
85
73
|
it 'returns false if the task has already been performed' do
|
86
74
|
task = ScheduledTask.new(0.1){ 42 }
|
87
|
-
task.run!
|
88
75
|
sleep(0.2)
|
89
76
|
task.cancel.should be_false
|
90
77
|
end
|
91
78
|
|
92
79
|
it 'returns false if the task is already in progress' do
|
93
80
|
task = ScheduledTask.new(0.1){ sleep(1); 42 }
|
94
|
-
task.run!
|
95
81
|
sleep(0.2)
|
96
82
|
task.cancel.should be_false
|
97
83
|
end
|
@@ -99,7 +85,6 @@ module Concurrent
|
|
99
85
|
it 'cancels the task if it has not yet started' do
|
100
86
|
@expected = true
|
101
87
|
task = ScheduledTask.new(0.3){ @expected = false }
|
102
|
-
task.run!
|
103
88
|
sleep(0.1)
|
104
89
|
task.cancel
|
105
90
|
sleep(0.5)
|
@@ -108,44 +93,25 @@ module Concurrent
|
|
108
93
|
|
109
94
|
it 'returns true on success' do
|
110
95
|
task = ScheduledTask.new(0.3){ @expected = false }
|
111
|
-
task.run!
|
112
96
|
sleep(0.1)
|
113
97
|
task.cancel.should be_true
|
114
98
|
end
|
115
99
|
|
116
100
|
it 'sets the state to :cancelled when cancelled' do
|
117
101
|
task = ScheduledTask.new(10){ 42 }
|
118
|
-
task.run!
|
119
102
|
sleep(0.1)
|
120
103
|
task.cancel
|
121
104
|
task.should be_cancelled
|
122
105
|
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
106
|
end
|
133
107
|
|
134
108
|
context 'execution' do
|
135
109
|
|
136
110
|
it 'sets the state to :in_progress when the task is running' do
|
137
111
|
task = ScheduledTask.new(0.1){ sleep(1); 42 }
|
138
|
-
task.run!
|
139
112
|
sleep(0.2)
|
140
113
|
task.should be_in_progress
|
141
114
|
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
115
|
end
|
150
116
|
|
151
117
|
context 'observation' do
|
@@ -167,13 +133,11 @@ module Concurrent
|
|
167
133
|
|
168
134
|
it 'returns true for an observer added while :pending' do
|
169
135
|
task = ScheduledTask.new(1){ 42 }
|
170
|
-
task.run!
|
171
136
|
task.add_observer(observer).should be_true
|
172
137
|
end
|
173
138
|
|
174
139
|
it 'returns true for an observer added while :in_progress' do
|
175
140
|
task = ScheduledTask.new(0.1){ sleep(1); 42 }
|
176
|
-
task.run!
|
177
141
|
sleep(0.2)
|
178
142
|
task.add_observer(observer).should be_true
|
179
143
|
end
|
@@ -185,7 +149,6 @@ module Concurrent
|
|
185
149
|
|
186
150
|
it 'returns false for an observer added once :cancelled' do
|
187
151
|
task = ScheduledTask.new(1){ 42 }
|
188
|
-
task.run!
|
189
152
|
sleep(0.1)
|
190
153
|
task.cancel
|
191
154
|
sleep(0.1)
|
@@ -194,14 +157,12 @@ module Concurrent
|
|
194
157
|
|
195
158
|
it 'returns false for an observer added once :fulfilled' do
|
196
159
|
task = ScheduledTask.new(0.1){ 42 }
|
197
|
-
task.run!
|
198
160
|
sleep(0.2)
|
199
161
|
task.add_observer(observer).should be_false
|
200
162
|
end
|
201
163
|
|
202
164
|
it 'returns false for an observer added once :rejected' do
|
203
165
|
task = ScheduledTask.new(0.1){ raise StandardError }
|
204
|
-
task.run!
|
205
166
|
sleep(0.2)
|
206
167
|
task.add_observer(observer).should be_false
|
207
168
|
end
|
@@ -209,7 +170,6 @@ module Concurrent
|
|
209
170
|
it 'notifies all observers on fulfillment' do
|
210
171
|
task = ScheduledTask.new(0.1){ 42 }
|
211
172
|
task.add_observer(observer)
|
212
|
-
task.run!
|
213
173
|
sleep(0.2)
|
214
174
|
task.value.should == 42
|
215
175
|
task.reason.should be_nil
|
@@ -220,7 +180,6 @@ module Concurrent
|
|
220
180
|
it 'notifies all observers on rejection' do
|
221
181
|
task = ScheduledTask.new(0.1){ raise StandardError }
|
222
182
|
task.add_observer(observer)
|
223
|
-
task.run!
|
224
183
|
sleep(0.2)
|
225
184
|
task.value.should be_nil
|
226
185
|
task.reason.should be_a(StandardError)
|
@@ -247,7 +206,6 @@ module Concurrent
|
|
247
206
|
it 'does not notify an observer added after cancellation' do
|
248
207
|
observer.should_not_receive(:update).with(any_args())
|
249
208
|
task = ScheduledTask.new(0.5){ 42 }
|
250
|
-
task.run!
|
251
209
|
sleep(0.1)
|
252
210
|
task.cancel
|
253
211
|
sleep(0.1)
|
@@ -65,19 +65,6 @@ module Concurrent
|
|
65
65
|
}.should raise_error
|
66
66
|
end
|
67
67
|
|
68
|
-
it 'passes the options to the new TimerTask' do
|
69
|
-
opts = {
|
70
|
-
execution_interval: 100,
|
71
|
-
timeout_interval: 100,
|
72
|
-
run_now: false,
|
73
|
-
logger: proc{ nil },
|
74
|
-
block_args: %w[one two three]
|
75
|
-
}
|
76
|
-
@subject = TimerTask.new(opts){ nil }
|
77
|
-
TimerTask.should_receive(:new).with(opts).and_return(@subject)
|
78
|
-
Concurrent::TimerTask.run!(opts)
|
79
|
-
end
|
80
|
-
|
81
68
|
it 'passes the block to the new TimerTask' do
|
82
69
|
@expected = false
|
83
70
|
block = proc{ @expected = true }
|
@@ -91,6 +78,30 @@ module Concurrent
|
|
91
78
|
Thread.should_receive(:new).with(any_args()).and_return(thread)
|
92
79
|
@subject = TimerTask.run!{ nil }
|
93
80
|
end
|
81
|
+
|
82
|
+
specify '#execution_interval is writeable' do
|
83
|
+
@subject = TimerTask.new(execution_interval: 1) do |task|
|
84
|
+
task.execution_interval = 3
|
85
|
+
end
|
86
|
+
@subject.execution_interval.should == 1
|
87
|
+
@subject.execution_interval = 0.1
|
88
|
+
@subject.execution_interval.should == 0.1
|
89
|
+
@thread = Thread.new { @subject.run }
|
90
|
+
sleep(0.2)
|
91
|
+
@subject.execution_interval.should == 3
|
92
|
+
end
|
93
|
+
|
94
|
+
specify '#execution_interval is writeable' do
|
95
|
+
@subject = TimerTask.new(timeout_interval: 1, execution_interval: 0.1) do |task|
|
96
|
+
task.timeout_interval = 3
|
97
|
+
end
|
98
|
+
@subject.timeout_interval.should == 1
|
99
|
+
@subject.timeout_interval = 2
|
100
|
+
@subject.timeout_interval.should == 2
|
101
|
+
@thread = Thread.new { @subject.run }
|
102
|
+
sleep(0.2)
|
103
|
+
@subject.timeout_interval.should == 3
|
104
|
+
end
|
94
105
|
end
|
95
106
|
end
|
96
107
|
|
@@ -126,15 +137,14 @@ module Concurrent
|
|
126
137
|
@expected.should be_true
|
127
138
|
end
|
128
139
|
|
129
|
-
it 'passes
|
130
|
-
args = [1,2,3,4]
|
140
|
+
it 'passes a "self" reference to the block as the sole argument' do
|
131
141
|
@expected = nil
|
132
|
-
@subject = TimerTask.new(execution_interval:
|
133
|
-
@expected =
|
142
|
+
@subject = TimerTask.new(execution_interval: 1, run_now: true) do |task|
|
143
|
+
@expected = task
|
134
144
|
end
|
135
145
|
@thread = Thread.new { @subject.run }
|
136
|
-
sleep(
|
137
|
-
@expected.should eq
|
146
|
+
sleep(0.2)
|
147
|
+
@expected.should eq @subject
|
138
148
|
end
|
139
149
|
|
140
150
|
it 'kills the worker thread if the timeout is reached' do
|
metadata
CHANGED
@@ -1,32 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.1.pre.
|
4
|
+
version: 0.3.1.pre.2
|
5
|
+
prerelease: 6
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Jerry D'Antonio
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2013-11-
|
12
|
+
date: 2013-11-06 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- - '>='
|
19
|
+
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '0'
|
20
22
|
type: :development
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- - '>='
|
27
|
+
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '0'
|
27
|
-
description:
|
28
|
-
|
29
|
-
|
30
|
+
description: ! " Modern concurrency tools including agents, futures, promises,
|
31
|
+
thread pools, actors, supervisors, and more.\n Inspired by Erlang, Clojure, Go,
|
32
|
+
JavaScript, actors, and classic concurrency patterns.\n"
|
30
33
|
email: jerry.dantonio@gmail.com
|
31
34
|
executables: []
|
32
35
|
extensions: []
|
@@ -95,29 +98,28 @@ files:
|
|
95
98
|
homepage: http://www.concurrent-ruby.com
|
96
99
|
licenses:
|
97
100
|
- MIT
|
98
|
-
|
99
|
-
|
100
|
-
future = Concurrent::Future.new{ 'Hello, world!' }
|
101
|
-
puts future.value
|
102
|
-
#=> Hello, world!
|
101
|
+
post_install_message: ! " future = Concurrent::Future.new{ 'Hello, world!' }\n
|
102
|
+
\ puts future.value\n #=> Hello, world!\n"
|
103
103
|
rdoc_options: []
|
104
104
|
require_paths:
|
105
105
|
- lib
|
106
106
|
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
107
108
|
requirements:
|
108
|
-
- - '>='
|
109
|
+
- - ! '>='
|
109
110
|
- !ruby/object:Gem::Version
|
110
111
|
version: 1.9.2
|
111
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
112
114
|
requirements:
|
113
|
-
- - '>'
|
115
|
+
- - ! '>'
|
114
116
|
- !ruby/object:Gem::Version
|
115
117
|
version: 1.3.1
|
116
118
|
requirements: []
|
117
119
|
rubyforge_project:
|
118
|
-
rubygems_version:
|
120
|
+
rubygems_version: 1.8.24
|
119
121
|
signing_key:
|
120
|
-
specification_version:
|
122
|
+
specification_version: 3
|
121
123
|
summary: Modern concurrency tools including agents, futures, promises, thread pools,
|
122
124
|
actors, and more.
|
123
125
|
test_files:
|
@@ -143,3 +145,4 @@ test_files:
|
|
143
145
|
- spec/concurrent/utilities_spec.rb
|
144
146
|
- spec/spec_helper.rb
|
145
147
|
- spec/support/functions.rb
|
148
|
+
has_rdoc:
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 86c7da12ad6b46036abb100e98a1450eda80fbb1
|
4
|
-
data.tar.gz: 43e1758062fd717a6e4a0c690890ef8149aa7b9e
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 1d46489016358e9138bf0a627617ef780d2feb8e9a0d2b1f2a4ac1aa25ff8ec94dab7c72ad5c62743adc8d6ce36b0b119d80c3aa57724685aa33788e541f2aea
|
7
|
-
data.tar.gz: b482b8b50ee0217bb0ed13dcb8d3c91f58984d660f91240484d867f72e7583b87fcde09558beb1349ecff39ba88e9d9ddc44863cb91f14a74d4109cf1ebcac05
|