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
@@ -1,191 +0,0 @@
1
- # To Gobbler's Knob. It's Groundhog Day!
2
-
3
- A very common currency pattern is to run a thread that performs a task at regular
4
- intervals. The thread that peforms the task sleeps for the given interval then
5
- wakes up and performs the task. Lather, rinse, repeat... This pattern causes two
6
- problems. First, it is difficult to test the business logic of the task becuse the
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.
27
-
28
- This class is based on the Java class
29
- [of the same name](http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html).
30
-
31
- ## Observation
32
-
33
- `TimerTask` supports notification through the Ruby standard library
34
- [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html)
35
- module. On execution the `TimerTask` will notify the observers with thress arguments:
36
- time of execution, the result of the block (or nil on failure), and any raised
37
- exceptions (or nil on success). If the timeout interval is exceeded the observer
38
- will receive a `Concurrent::TimeoutError` object as the third argument.
39
-
40
- ## Examples
41
-
42
- A basic example:
43
-
44
- ```ruby
45
- require 'concurrent'
46
-
47
- task = Concurrent::TimerTask.new{ puts 'Boom!' }
48
- task.run!
49
-
50
- task.execution_interval #=> 60 (default)
51
- task.timeout_interval #=> 30 (default)
52
-
53
- # wait 60 seconds...
54
- #=> 'Boom!'
55
-
56
- task.stop #=> true
57
- ```
58
-
59
- Both the execution_interval and the timeout_interval can be configured:
60
-
61
- ```ruby
62
- task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
63
- puts 'Boom!'
64
- end
65
-
66
- task.execution_interval #=> 5
67
- task.timeout_interval #=> 5
68
- ```
69
-
70
- By default an `TimerTask` will wait for `:execution_interval` seconds before running the block.
71
- To run the block immediately set the `:run_now` option to `true`:
72
-
73
- ```ruby
74
- task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
75
- task.run!
76
-
77
- #=> 'Boom!'
78
- ```
79
-
80
- The `TimerTask` class includes the `Dereferenceable` mixin module so the result of
81
- the last execution is always available via the `#value` method. Derefencing options
82
- can be passed to the `TimerTask` during construction or at any later time using the
83
- `#set_deref_options` method.
84
-
85
- ```ruby
86
- task = Concurrent::TimerTask.new(
87
- dup_on_deref: true,
88
- execution_interval: 5
89
- ){ Time.now }
90
-
91
- task.run!
92
- Time.now #=> 2013-11-07 18:06:50 -0500
93
- sleep(10)
94
- task.value #=> 2013-11-07 18:06:55 -0500
95
- ```
96
-
97
- A simple example with observation:
98
-
99
- ```ruby
100
- class TaskObserver
101
- def update(time, result, ex)
102
- if result
103
- print "(#{time}) Execution successfully returned #{result}\n"
104
- elsif ex.is_a?(Concurrent::TimeoutError)
105
- print "(#{time}) Execution timed out\n"
106
- else
107
- print "(#{time}) Execution failed with error #{ex}\n"
108
- end
109
- end
110
- end
111
-
112
- task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ 42 }
113
- task.add_observer(TaskObserver.new)
114
- task.run!
115
-
116
- #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42
117
- #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42
118
- #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42
119
- task.stop
120
-
121
- task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ sleep }
122
- task.add_observer(TaskObserver.new)
123
- task.run!
124
-
125
- #=> (2013-10-13 19:07:25 -0400) Execution timed out
126
- #=> (2013-10-13 19:07:27 -0400) Execution timed out
127
- #=> (2013-10-13 19:07:29 -0400) Execution timed out
128
- task.stop
129
-
130
- task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError }
131
- task.add_observer(TaskObserver.new)
132
- task.run!
133
-
134
- #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError
135
- #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError
136
- #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError
137
- task.stop
138
- ```
139
-
140
- In some cases it may be necessary for a `TimerTask` to affect its own execution cycle.
141
- To facilitate this a reference to the task object is passed into the block as a block
142
- argument every time the task is executed.
143
-
144
- ```ruby
145
- timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
146
- task.execution_interval.times{ print 'Boom! ' }
147
- print "\n"
148
- task.execution_interval += 1
149
- if task.execution_interval > 5
150
- puts 'Stopping...'
151
- task.stop
152
- end
153
- end
154
-
155
- timer_task.run # blocking call - this task will stop itself
156
- #=> Boom!
157
- #=> Boom! Boom!
158
- #=> Boom! Boom! Boom!
159
- #=> Boom! Boom! Boom! Boom!
160
- #=> Boom! Boom! Boom! Boom! Boom!
161
- #=> Stopping...
162
- ```
163
-
164
- ## Copyright
165
-
166
- *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
167
- It is free software and may be redistributed under the terms specified in the LICENSE file.
168
-
169
- ## License
170
-
171
- Released under the MIT license.
172
-
173
- http://www.opensource.org/licenses/mit-license.php
174
-
175
- > Permission is hereby granted, free of charge, to any person obtaining a copy
176
- > of this software and associated documentation files (the "Software"), to deal
177
- > in the Software without restriction, including without limitation the rights
178
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
179
- > copies of the Software, and to permit persons to whom the Software is
180
- > furnished to do so, subject to the following conditions:
181
- >
182
- > The above copyright notice and this permission notice shall be included in
183
- > all copies or substantial portions of the Software.
184
- >
185
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
186
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
187
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
188
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
189
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
190
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
191
- > THE SOFTWARE.
@@ -1,34 +0,0 @@
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
@@ -1,240 +0,0 @@
1
- require 'spec_helper'
2
-
3
- require 'concurrent/agent'
4
- require 'concurrent/future'
5
- require 'concurrent/promise'
6
-
7
- if mri?
8
-
9
- module Concurrent
10
-
11
- describe EventMachineDeferProxy do
12
-
13
- subject { EventMachineDeferProxy.new }
14
-
15
- after(:all) do
16
- $GLOBAL_THREAD_POOL = FixedThreadPool.new(1)
17
- end
18
-
19
- context '#post' do
20
-
21
- it 'proxies a call without arguments' do
22
- @expected = false
23
- EventMachine.run do
24
- subject.post{ @expected = true }
25
- sleep(0.1)
26
- EventMachine.stop
27
- end
28
- @expected.should eq true
29
- end
30
-
31
- it 'proxies a call with arguments' do
32
- @expected = []
33
- EventMachine.run do
34
- subject.post(1,2,3){|*args| @expected = args }
35
- sleep(0.1)
36
- EventMachine.stop
37
- end
38
- @expected.should eq [1,2,3]
39
- end
40
-
41
- it 'aliases #<<' do
42
- @expected = false
43
- EventMachine.run do
44
- subject << proc{ @expected = true }
45
- sleep(0.1)
46
- EventMachine.stop
47
- end
48
- @expected.should eq true
49
- end
50
- end
51
-
52
- context 'operation' do
53
-
54
- context Agent do
55
-
56
- subject { Agent.new(0) }
57
-
58
- before(:each) do
59
- Agent.thread_pool = EventMachineDeferProxy.new
60
- end
61
-
62
- it 'supports fulfillment' do
63
-
64
- EventMachine.run do
65
-
66
- @expected = []
67
- subject.post{ @expected << 1 }
68
- subject.post{ @expected << 2 }
69
- subject.post{ @expected << 3 }
70
- sleep(0.1)
71
- @expected.should include(1)
72
- @expected.should include(2)
73
- @expected.should include(3)
74
-
75
- EventMachine.stop
76
- end
77
- end
78
-
79
- it 'supports validation' do
80
-
81
- EventMachine.run do
82
-
83
- @expected = nil
84
- subject.validate{ @expected = 10; true }
85
- subject.post{ nil }
86
- sleep(0.1)
87
- @expected.should eq 10
88
-
89
- EventMachine.stop
90
- end
91
- end
92
-
93
- it 'supports rejection' do
94
-
95
- EventMachine.run do
96
-
97
- @expected = nil
98
- subject.
99
- on_error(StandardError){|ex| @expected = 1 }.
100
- on_error(StandardError){|ex| @expected = 2 }.
101
- on_error(StandardError){|ex| @expected = 3 }
102
- subject.post{ raise StandardError }
103
- sleep(0.1)
104
- @expected.should eq 1
105
-
106
- EventMachine.stop
107
- end
108
- end
109
- end
110
-
111
- context Future do
112
-
113
- before(:each) do
114
- Future.thread_pool = EventMachineDeferProxy.new
115
- end
116
-
117
- it 'supports fulfillment' do
118
-
119
- EventMachine.run do
120
-
121
- @a = @b = @c = nil
122
- f = Future.new(1, 2, 3) do |a, b, c|
123
- @a, @b, @c = a, b, c
124
- end
125
- sleep(0.1)
126
- [@a, @b, @c].should eq [1, 2, 3]
127
-
128
- sleep(0.1)
129
- EventMachine.stop
130
- end
131
- end
132
- end
133
-
134
- context Promise do
135
-
136
- before(:each) do
137
- Promise.thread_pool = EventMachineDeferProxy.new
138
- end
139
-
140
- context 'fulfillment' do
141
-
142
- it 'passes all arguments to the first promise in the chain' do
143
-
144
- EventMachine.run do
145
-
146
- @a = @b = @c = nil
147
- p = Promise.new(1, 2, 3) do |a, b, c|
148
- @a, @b, @c = a, b, c
149
- end
150
- sleep(0.1)
151
- [@a, @b, @c].should eq [1, 2, 3]
152
-
153
- sleep(0.1)
154
- EventMachine.stop
155
- end
156
- end
157
-
158
- it 'passes the result of each block to all its children' do
159
-
160
- EventMachine.run do
161
- @expected = nil
162
- Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
163
- sleep(0.1)
164
- @expected.should eq 20
165
-
166
- sleep(0.1)
167
- EventMachine.stop
168
- end
169
- end
170
-
171
- it 'sets the promise value to the result if its block' do
172
-
173
- EventMachine.run do
174
-
175
- p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
176
- sleep(0.1)
177
- p.value.should eq 40
178
-
179
- sleep(0.1)
180
- EventMachine.stop
181
- end
182
- end
183
- end
184
-
185
- context 'rejection' do
186
-
187
- it 'sets the promise reason and error on exception' do
188
-
189
- EventMachine.run do
190
-
191
- p = Promise.new{ raise StandardError.new('Boom!') }
192
- sleep(0.1)
193
- p.reason.should be_a(Exception)
194
- p.reason.should.to_s =~ /Boom!/
195
- p.should be_rejected
196
-
197
- sleep(0.1)
198
- EventMachine.stop
199
- end
200
- end
201
-
202
- it 'calls the first exception block with a matching class' do
203
-
204
- EventMachine.run do
205
-
206
- @expected = nil
207
- Promise.new{ raise StandardError }.
208
- on_error(StandardError){|ex| @expected = 1 }.
209
- on_error(StandardError){|ex| @expected = 2 }.
210
- on_error(StandardError){|ex| @expected = 3 }
211
- sleep(0.1)
212
- @expected.should eq 1
213
-
214
- sleep(0.1)
215
- EventMachine.stop
216
- end
217
- end
218
-
219
- it 'passes the exception object to the matched block' do
220
-
221
- EventMachine.run do
222
-
223
- @expected = nil
224
- Promise.new{ raise StandardError }.
225
- on_error(ArgumentError){|ex| @expected = ex }.
226
- on_error(LoadError){|ex| @expected = ex }.
227
- on_error(Exception){|ex| @expected = ex }
228
- sleep(0.1)
229
- @expected.should be_a(StandardError)
230
-
231
- sleep(0.1)
232
- EventMachine.stop
233
- end
234
- end
235
- end
236
- end
237
- end
238
- end
239
- end
240
- end