concurrent-ruby 0.4.1 → 0.5.0.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.
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