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 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 future of concurrency
24
- is asynchronous. Send out a bunch of independent [actors](http://en.wikipedia.org/wiki/Actor_model)
25
- to do your bidding and process the results when you are ready. Although the idea of the concurrent
26
- actor originated in the early 1970's it has only recently started catching on. Although there is
27
- no one "true" actor implementation (what *exactly* is "object oriented," what *exactly* is
28
- "concurrent programming"), many modern programming languages implement variations on the actor
29
- theme. This library implements a few of the most interesting and useful of those variations.
30
-
31
- Remember, *there is no silver bullet in concurrent programming.* Concurrency is hard. Very hard.
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
- ### Goals
45
-
46
- My history with high-performance, highly-concurrent programming goes back to my days with C/C++.
47
- I have the same scars as everyone else doing that kind of work with those languages.
48
- I'm fascinated by modern concurrency patterns like [Actors](http://en.wikipedia.org/wiki/Actor_model),
49
- [Agents](http://doc.akka.io/docs/akka/snapshot/java/agents.html), and
50
- [Promises](http://promises-aplus.github.io/promises-spec/). I'm equally fascinated by languages
51
- with strong concurrency support like [Erlang](http://www.erlang.org/doc/getting_started/conc_prog.html),
52
- [Go](http://golang.org/doc/articles/concurrency_patterns.html), and
53
- [Clojure](http://clojure.org/concurrent_programming). My goal is to implement those patterns in Ruby.
54
- Specifically:
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 @state == :pending || @state == :in_progress
61
+ return false unless [:pending, :in_progress].include?(@state)
61
62
  super
62
63
  end
63
64
 
64
65
  protected
65
66
 
66
- def on_task
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
- attr_reader :execution_interval
17
- attr_reader :timeout_interval
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(*@block_args)
73
+ Thread.current[:result] = @task.call(self)
71
74
  end
72
75
  raise TimeoutError if @worker.join(@timeout_interval).nil?
73
- changed
74
- notify_observers(Time.now, @worker[:result], nil)
76
+ mutex.synchronize { @value = @worker[:result] }
75
77
  rescue Exception => ex
76
- changed
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
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.3.1.pre.1'
2
+ VERSION = '0.3.1.pre.2'
3
3
  end
@@ -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
- waked up and performs the task. Later, rinse, repeat... This pattern causes two
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 couple with the threading. Second, an exception in the task
8
- can cause the entire thread to abend. In a long-running application where the task
9
- thread is intended to run for days/weeks/years a crashed task thread can pose a real
10
- problem. The `TimerTask` class 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 separat thread. The advantage of this approach is that if
15
- the task crashes it will only kill the task thread, not the TimerTask thread. The
16
- TimerTask thread can then log the success or failure of the task. The TimerTask
17
- can even be configured with a timeout value allowing it to kill a task that runs
18
- to long and then log the error.
19
-
20
- One other advantage of the `TimerTask` class is that it forces the bsiness logic to
21
- be completely decoupled from the threading logic. The business logic can be tested
22
- separately then passed to the a TimerTask for scheduling and running.
23
-
24
- The `TimerTask` is the yin to to the
25
- [Supervisor's](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md)
26
- yang. Where the `Supervisor` is intended to manage long-running threads that operate
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
- task = ScheduledTask.new(1){ fulfilled_value }
24
- task.run!
25
- task
17
+ ScheduledTask.new(1){ fulfilled_value }
26
18
  end
27
19
 
28
20
  let(:fulfilled_subject) do
29
- task = ScheduledTask.new(0.1){ fulfilled_value }
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
- task = ScheduledTask.new(0.1){ raise rejected_reason }
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 any given arguments to the execution block' do
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: 0.5, args: args) do |*args|
133
- @expected = args
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(1)
137
- @expected.should eq args
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.1
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-03 00:00:00.000000000 Z
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: |2
28
- Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more.
29
- Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns.
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
- metadata: {}
99
- post_install_message: |2
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: 2.1.10
120
+ rubygems_version: 1.8.24
119
121
  signing_key:
120
- specification_version: 4
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