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,156 +0,0 @@
1
- # I'm late! For a very important date!
2
-
3
- `ScheduledTask` is a close relative of `Concurrent::Future` but with one
4
- important difference. A `Future` is set to execute as soon as possible
5
- whereas a `ScheduledTask` is set to execute at a specific time. This implementation
6
- is loosely based on Java's
7
- [ScheduledExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html).
8
-
9
- ## Scheduling
10
-
11
- The scheduled time of task execution is set on object construction. The first
12
- parameter to `#new` is the task execution time. The time can be a numeric (floating
13
- point or integer) representing a number of seconds in the future or it can ba a
14
- `Time` object representing the approximate time of execution. Any other value,
15
- a numeric equal to or less than zero, or a time in the past will result in
16
- an `ArgumentError` being raised.
17
-
18
- The constructor can also be given zero or more processing options. Currently the
19
- only supported options are those recognized by the
20
- [Dereferenceable](https://github.com/jdantonio/concurrent-ruby/blob/master/md/dereferenceable.md)
21
- module.
22
-
23
- The final constructor argument is a block representing the task to be performed
24
- at the scheduled time. If no block is given an `ArgumentError` will be raised.
25
-
26
- ### States
27
-
28
- `ScheduledTask` mixes in the
29
- [Obligation](https://github.com/jdantonio/concurrent-ruby/blob/master/md/obligation.md)
30
- module thus giving it "future" behavior. This includes the expected lifecycle states.
31
- `ScheduledTask` has one additional state, however. While the task (block) is being
32
- executed the state of the object will be `:in_progress`. This additional state is
33
- necessary because it has implications for task cancellation.
34
-
35
- ### Cancellation
36
-
37
- A `:pending` task can be cancelled using the `#cancel` method. A task in any other
38
- state, including `:in_progress`, cannot be cancelled. The `#cancel` method returns
39
- a boolean indicating the success of the cancellation attempt. A cancelled `ScheduledTask`
40
- cannot be restarted. It is immutable.
41
-
42
- ## Obligation and Observation
43
-
44
- The result of a `ScheduledTask` can be obtained either synchronously or asynchronously.
45
- `ScheduledTask` mixes in both the
46
- [Obligation](https://github.com/jdantonio/concurrent-ruby/blob/master/md/obligation.md)
47
- module and the
48
- [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html)
49
- module from the Ruby standard library. With one exception `ScheduledTask` behaves
50
- identically to
51
- [Concurrent::Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
52
- with regard to these modules.
53
-
54
- Unlike `Future`, however, an observer added to a `ScheduledTask` *after* the task
55
- operation has completed will *not* receive notification. The reason for this is the
56
- subtle but important difference in intent between the two abstractions. With a
57
- `Future` there is no way to know when the operation will complete. Therefore the
58
- *expected* behavior of an observer is to be notified. With a `ScheduledTask` however,
59
- the approximate time of execution is known. It is often explicitly set as a constructor
60
- argument. It is always available via the `#schedule_time` attribute reader. Therefore
61
- it is always possible for calling code to know whether the observer is being added
62
- prior to task execution. It is also easy to add an observer long before task
63
- execution begins (since there is never a reason to create a scheduled task that starts
64
- immediately). Subsequently, the *expectation* is that the caller of `#add_observer`
65
- is making the call within an appropriate time.
66
-
67
- ## Examples
68
-
69
- Successful task execution using seconds for scheduling:
70
-
71
- ```ruby
72
- require 'concurrent'
73
-
74
- task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }
75
- task.pending? #=> true
76
- task.schedule_time #=> 2013-11-07 12:20:07 -0500
77
-
78
- # wait for it...
79
- sleep(3)
80
-
81
- task.pending? #=> false
82
- task.fulfilled? #=> true
83
- task.rejected? #=> false
84
- task.value #=> 'What does the fox say?'
85
- ```
86
-
87
- Failed task execution using a `Time` object for scheduling:
88
-
89
- ```ruby
90
- require 'concurrent'
91
-
92
- t = Time.now + 2
93
- task = Concurrent::ScheduledTask.new(t){ raise StandardError.new('Call me maybe?') }
94
- task.pending? #=> true
95
- task.schedule_time #=> 2013-11-07 12:22:01 -0500
96
-
97
- # wait for it...
98
- sleep(3)
99
-
100
- task.pending? #=> false
101
- task.fulfilled? #=> false
102
- task.rejected? #=> true
103
- task.value #=> nil
104
- task.reason #=> #<StandardError: Call me maybe?>
105
- ```
106
-
107
- Task execution with observation:
108
-
109
- ```ruby
110
- require 'concurrent'
111
-
112
- observer = Class.new{
113
- def update(time, value, reason)
114
- puts "The task completed at #{time} with value '#{value}'"
115
- end
116
- }.new
117
-
118
- task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }
119
- task.add_observer(observer)
120
- task.pending? #=> true
121
- task.schedule_time #=> 2013-11-07 12:20:07 -0500
122
-
123
- # wait for it...
124
- sleep(3)
125
-
126
- #>> The task completed at 2013-11-07 12:26:09 -0500 with value 'What does the fox say?'
127
- ```
128
-
129
- ## Copyright
130
-
131
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
132
- It is free software and may be redistributed under the terms specified in the LICENSE file.
133
-
134
- ## License
135
-
136
- Released under the MIT license.
137
-
138
- http://www.opensource.org/licenses/mit-license.php
139
-
140
- > Permission is hereby granted, free of charge, to any person obtaining a copy
141
- > of this software and associated documentation files (the "Software"), to deal
142
- > in the Software without restriction, including without limitation the rights
143
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
144
- > copies of the Software, and to permit persons to whom the Software is
145
- > furnished to do so, subject to the following conditions:
146
- >
147
- > The above copyright notice and this permission notice shall be included in
148
- > all copies or substantial portions of the Software.
149
- >
150
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
151
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
152
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
153
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
154
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
155
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
156
- > THE SOFTWARE.
@@ -1,246 +0,0 @@
1
- # You don't need to get no supervisor! You the supervisor today!
2
-
3
- One of Erlang's claim to fame is its fault tolerance. Erlang systems have been known
4
- to exhibit near-mythical levels of uptime. One of the main reasons is the pervaisve
5
- design philosophy of "let it fail." When errors occur most Erlang systems simply let
6
- the failing component fail completely. The system then restarts the failed component.
7
- This "let it fail" resilience isn't an intrinsic capability of either the language
8
- or the virtual machine. It's a deliberate design philosophy. One of the key enablers
9
- of this philosophy is the [Supervisor](http://www.erlang.org/doc/man/supervisor.html)
10
- of the OTP (standard library).
11
-
12
- The Supervisor module answers the question "Who watches the watchmen?" A single
13
- Supervisor can manage any number of workers (children). The Supervisor assumes
14
- responsibility for starting the children, stopping them, and restarting them if
15
- they fail. Several classes in this library, including `Actor` and `TimerTask` are
16
- designed to work with `Supervisor`. Additionally, `Supervisor`s can supervise others
17
- `Supervisor`s (see *Supervision Trees* below).
18
-
19
- The `Concurrent::Supervisor` class is a faithful and nearly complete implementaion
20
- of Erlang's Supervisor module.
21
-
22
- ## Basic Supervisor Behavior
23
-
24
- At the core a `Supervisor` instance is a very simple object. Simply create a `Supervisor`,
25
- add at least one worker using the `#add_worker` method, and start the `Supervisor` using
26
- either `#run` (blocking) or `#run!` (non-blocking). The `Supervisor` will spawn a new thread
27
- for each child and start the chid on its thread. The `Supervisor` will then continuously
28
- monitor all its child threads. If any of the children crash the `Supervisor` will restart
29
- them in accordance with its *restart strategy* (see below). Later, stop the `Supervisor`
30
- with its `#stop` method and it will gracefully stop all its children.
31
-
32
- A `Supervisor` will also track the number of times it must restart children withing a
33
- defined, sliding window of time. If the onfigured threshholds are exceeded (see *Intervals*
34
- below) then the `Supervisor` will assume there is a catastrophic failure (possibly within
35
- the `Supervisor` itself) and it will shut itself down. If the `Supervisor` is part of a
36
- *supervision tree* (see below) then its `Supervisor` will likely restart it.
37
-
38
- ```ruby
39
- task = Concurrent::TimerTask.new{ print "[#{Time.now}] Hello world!\n" }
40
-
41
- supervisor = Concurrent::Supervisor.new
42
- supervisor.add_worker(task)
43
-
44
- supervisor.run! # the #run method blocks, #run! does not
45
- ```
46
-
47
- ## Workers
48
-
49
- Any object can be managed by a `Supervisor` so long as the class to be supervised supports
50
- the required API. A supervised object needs only support three methods:
51
-
52
- * `#run` is a blocking call that starts the child then blocks until the child is stopped
53
- * `#running?` is a predicate method indicating whether or not the child is running
54
- * `#stop` gracefully stops the child if it is running
55
-
56
- ### Runnable
57
-
58
- To facilitate the creation of supervisorable classes, the `Runnable` module is provided.
59
- Simple include `Runnable` in the class and the required API methods will be provided.
60
- `Runnable` also provides several lifecycle methods that may be overridden by the including
61
- class. At a minimum the `#on_task` method *must* be overridden. `Runnable` will provide an
62
- infinite loop that will start when either the `#run` or `#run!` method is called. The subclass
63
- `#on_task` method will be called once in every iteration. The overridden method should provide
64
- some sort of blocking behavior otherwise the run loop may monopolize the processor and spike
65
- the processor utilization.
66
-
67
- The following optional lifecycle methods are also provided:
68
-
69
- * `#on_run` is called once when the object is started via the `#run` or `#run!` method but before the `#on_task` method is first called
70
- * `#on_stop` is called once when the `#stop` method is called, after the last call to `#on_task`
71
-
72
- ```ruby
73
- class Echo
74
- include Concurrent::Runnable
75
-
76
- def initialize
77
- @queue = Queue.new
78
- end
79
-
80
- def post(message)
81
- @queue.push(message)
82
- end
83
-
84
- protected
85
-
86
- def on_task
87
- message = @queue.pop
88
- print "#{message}\n"
89
- end
90
- end
91
-
92
- echo = Echo.new
93
- supervisor = Concurrent::Supervisor.new
94
- supervisor.add_worker(echo)
95
- supervisor.run!
96
- ```
97
-
98
- ## Supervisor Configuration
99
-
100
- A newly-created `Supervisor` will be configured with a reasonable set of options that should
101
- suffice for most purposes. In many cases no additional configuration will be required. When
102
- more granular control is required a `Supervisor` may be given several configuration options
103
- during initialization. Additionally, a few per-worker configuration options may be passed
104
- during the call to `#add_worker`. Once a `Supervisor` is created and the workers are added
105
- no additional configuration is possible.
106
-
107
- ### Intervals
108
-
109
- A `Supervisor` monitors its children and conducts triage operations based on several configurable
110
- intervals:
111
-
112
- * `:monitor_interval` specifies the number of seconds between health checks of the workers. The
113
- higher the interval the longer a particular worker may be dead before being restarted. The
114
- default is 1 second.
115
- * `:max_restart` specifies the number of times (in total) the `Supevisor` may restart children
116
- before it assumes there is a catastrophic failure and it shuts itself down. The default is 5
117
- restarts.
118
- * `:max_time` if the time interval over which `#max_restart` is tracked. Since `Supervisor` is
119
- intended to be used in applications that may run forever the `#max_restart` count must be
120
- timeboxed to prevent erroneous `Supervisor shutdown`. The default is 60 seconds.
121
-
122
- ### Restart Strategy
123
-
124
- When a child thread dies the `Supervisor` will restart it, and possibly other children,
125
- with the expectation that the workers are capable of cleaning themselves up and running
126
- again. The `Supervisor` will call each targetted worker's `#stop` method, kill the
127
- worker's thread, spawn a new thread, and call the worker's `#run` method.
128
-
129
- * `:one_for_one` When this restart strategy is set the `Supervisor` will only restart
130
- the worker thread that has died. It will not restart any of the other children.
131
- This is the default restart strategy.
132
- * `:one_for_all` When this restart strategy is set the `Supervisor` will restart all
133
- children when any one child dies. All workers will be stopped in the order they were
134
- originally added to the `Supervisor`. Once all childrean have been stopped they will
135
- all be started again in the same order.
136
- * `:rest_for_one` This restart strategy assumes that the order the workers were added
137
- to the `Supervisor` is meaningful. When one child dies all the downstream children
138
- (children added to the `Supervisor` after the dead worker) will be restarted. The
139
- `Supervisor` will begin by calling the `#stop` method on the dead worker and all
140
- downstream workers. The `Supervisor` will then iterate over all dead workers and
141
- restart each by creating a new thread then calling the worker's `#run` method.
142
-
143
- When a restart is initiated under any strategy other than `:one_for_one` the
144
- `:max_restart` value will only be incremented by one, regardless of how many children
145
- are restarted.
146
-
147
- ### Worker Restart Option
148
-
149
- When a worker dies the default behavior of the `Supervisor` is to restart one or more
150
- workers according to the restart strategy defined when the `Supervisor` is created
151
- (see above). This behavior can be modified on a per-worker basis using the `:restart`
152
- option when calling `#add_worker`. Three worker `:restart` options are supported:
153
-
154
- * `:permanent` means the worker is intended to run forever and will always be restarted
155
- (this is the default)
156
- * `:temporary` workers are expected to stop on their own as a normal part of their operation
157
- and will only be restarted on an abnormal exit
158
- * `:transient` workers will never be restarted
159
-
160
- ### Worker Type
161
-
162
- Every worker added to a `Supervisor` is of either type `:worker` or `:supervisor`. The defauly
163
- value is `:worker`. Currently this type makes no functional difference. It is purely informational.
164
-
165
- ## Supervision Trees
166
-
167
- One of the most powerful aspects of Erlang's supervisor module is its ability to supervise
168
- other supervisors. This allows for the creation of deep, robust *supervision trees*.
169
- Workers can be gouped under multiple bottom-level `Supervisor`s. Each of these `Supervisor`s
170
- can be configured according to the needs of its workers. These multiple `Supervisor`s can
171
- be added as children to another `Supervisor`. The root `Supervisor` can then start the
172
- entire tree via trickel-down (start its children which start their children and so on).
173
- The root `Supervisor` then monitor its child `Supervisor`s, and so on.
174
-
175
- Supervision trees are the main reason that a `Supervisor` will shut itself down if its
176
- `:max_restart`/`:max_time` threshhold is exceeded. An isolated `Supervisor` will simply
177
- shut down forever. A `Supervisor` that is part of a supervision tree will shut itself
178
- down and let its parent `Supervisor` manage the restart.
179
-
180
- ## Examples
181
-
182
- ```ruby
183
- QUERIES = %w[YAHOO Microsoft google]
184
-
185
- class FinanceActor < Concurrent::Actor
186
- def act(query)
187
- finance = Finance.new(query)
188
- print "[#{Time.now}] RECEIVED '#{query}' to #{self} returned #{finance.update.suggested_symbols}\n\n"
189
- end
190
- end
191
-
192
- financial, pool = FinanceActor.pool(5)
193
-
194
- timer_proc = proc do
195
- query = QUERIES[rand(QUERIES.length)]
196
- financial.post(query)
197
- print "[#{Time.now}] SENT '#{query}' from #{self} to worker pool\n\n"
198
- end
199
-
200
- t1 = Concurrent::TimerTask.new(execution_interval: rand(5)+1, &timer_proc)
201
- t2 = Concurrent::TimerTask.new(execution_interval: rand(5)+1, &timer_proc)
202
-
203
- overlord = Concurrent::Supervisor.new
204
-
205
- overlord.add_worker(t1)
206
- overlord.add_worker(t2)
207
- pool.each{|actor| overlord.add_worker(actor)}
208
-
209
- overlord.run!
210
- ```
211
-
212
- ## Additional Reading
213
-
214
- * [Supervisor Module](http://www.erlang.org/doc/man/supervisor.html)
215
- * [Supervisor Behaviour](http://www.erlang.org/doc/design_principles/sup_princ.html)
216
- * [Who Supervises The Supervisors?](http://learnyousomeerlang.com/supervisors)
217
- * [OTP Design Principles](http://www.erlang.org/doc/design_principles/des_princ.html)
218
-
219
- ## Copyright
220
-
221
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
222
- It is free software and may be redistributed under the terms specified in the LICENSE file.
223
-
224
- ## License
225
-
226
- Released under the MIT license.
227
-
228
- http://www.opensource.org/licenses/mit-license.php
229
-
230
- > Permission is hereby granted, free of charge, to any person obtaining a copy
231
- > of this software and associated documentation files (the "Software"), to deal
232
- > in the Software without restriction, including without limitation the rights
233
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
234
- > copies of the Software, and to permit persons to whom the Software is
235
- > furnished to do so, subject to the following conditions:
236
- >
237
- > The above copyright notice and this permission notice shall be included in
238
- > all copies or substantial portions of the Software.
239
- >
240
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
241
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
242
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
243
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
244
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
245
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
246
- > THE SOFTWARE.
@@ -1,225 +0,0 @@
1
- # We're Going to Need a Bigger Boat
2
-
3
- Thread pools are neither a new idea nor an implementation of the actor pattern. Nevertheless, thread
4
- pools are still an extremely relevant concurrency tool. Every time a thread is created then
5
- subsequently destroyed there is overhead. Creating a pool of reusable worker threads then repeatedly'
6
- dipping into the pool can have huge performace benefits for a long-running application like a service.
7
- Ruby's blocks provide an excellent mechanism for passing a generic work request to a thread, making
8
- Ruby an excellent candidate language for thread pools.
9
-
10
- The inspiration for thread pools in this library is Java's `java.util.concurrent` implementation of
11
- [thread pools](java.util.concurrent). The `java.util.concurrent` library is a well-designed, stable,
12
- scalable, and battle-tested concurrency library. It provides three different implementations of thread
13
- pools. One of those implementations is simply a special case of the first and doesn't offer much
14
- advantage in Ruby, so only the first two (`FixedThreadPool` and `CachedThreadPool`) are implemented here.
15
-
16
- Thread pools share common `behavior` defined by `:thread_pool`. The most imortant method is `post`
17
- (aliased with the left-shift operator `<<`). The `post` method sends a block to the pool for future
18
- processing.
19
-
20
- A running thread pool can be shutdown in an orderly or disruptive manner. Once a thread pool has been
21
- shutdown in cannot be started again. The `shutdown` method can be used to initiate an orderly shutdown
22
- of the thread pool. All new `post` calls will reject the given block and immediately return `false`.
23
- Threads in the pool will continue to process all in-progress work and will process all tasks still in
24
- the queue. The `kill` method can be used to immediately shutdown the pool. All new `post` calls will
25
- reject the given block and immediately return `false`. Ruby's `Thread.kill` will be called on all threads
26
- in the pool, aborting all in-progress work. Tasks in the queue will be discarded.
27
-
28
- A client thread can choose to block and wait for pool shutdown to complete. This is useful when shutting
29
- down an application and ensuring the app doesn't exit before pool processing is complete. The method
30
- `wait_for_termination` will block for a maximum of the given number of seconds then return `true` if
31
- shutdown completed successfully or `false`. When the timeout value is `nil` the call will block
32
- indefinitely. Calling `wait_for_termination` on a stopped thread pool will immediately return `true`.
33
-
34
- Predicate methods are provided to describe the current state of the thread pool. Provided methods are
35
- `running?`, `shutdown?`, and `killed?`. The `shutdown` method will return true regardless of whether
36
- the pool was shutdown wil `shutdown` or `kill`.
37
-
38
- ## FixedThreadPool
39
-
40
- From the docs:
41
-
42
- > Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
43
- > At any point, at most `nThreads` threads will be active processing tasks. If additional tasks are submitted
44
- > when all threads are active, they will wait in the queue until a thread is available. If any thread terminates
45
- > due to a failure during execution prior to shutdown, a new one will take its place if needed to execute
46
- > subsequent tasks. The threads in the pool will exist until it is explicitly `shutdown`.
47
-
48
- ### Examples
49
-
50
- ```ruby
51
- require 'concurrent'
52
-
53
- pool = Concurrent::FixedThreadPool.new(5)
54
-
55
- pool.size #=> 5
56
- pool.running? #=> true
57
- pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
58
-
59
- pool.post(1,2,3){|*args| sleep(10) }
60
- pool << proc{ sleep(10) }
61
- pool.size #=> 5
62
-
63
- sleep(11)
64
- pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
65
-
66
- pool.shutdown #=> :shuttingdown
67
- pool.status #=> []
68
- pool.wait_for_termination
69
-
70
- pool.size #=> 0
71
- pool.status #=> []
72
- pool.shutdown? #=> true
73
- ```
74
-
75
- ## CachedThreadPool
76
-
77
- From the docs:
78
-
79
- > Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when
80
- > they are available. These pools will typically improve the performance of programs that execute many short-lived
81
- > asynchronous tasks. Calls to [`post`] will reuse previously constructed threads if available. If no existing
82
- > thread is available, a new thread will be created and added to the pool. Threads that have not been used for
83
- > sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will
84
- > not consume any resources. Note that pools with similar properties but different details (for example,
85
- > timeout parameters) may be created using [`CachedThreadPool`] constructors.
86
-
87
- ### Examples
88
-
89
- ```ruby
90
- require 'concurrent'
91
-
92
- pool = Concurrent::CachedThreadPool.new
93
-
94
- pool.size #=> 0
95
- pool.running? #=> true
96
- pool.status #=> []
97
-
98
- pool.post(1,2,3){|*args| sleep(10) }
99
- pool << proc{ sleep(10) }
100
- pool.size #=> 2
101
- pool.status #=> [[:working, nil, "sleep"], [:working, nil, "sleep"]]
102
-
103
- sleep(11)
104
- pool.status #=> [[:idle, 23, "sleep"], [:idle, 23, "sleep"]]
105
-
106
- sleep(60)
107
- pool.size #=> 0
108
- pool.status #=> []
109
-
110
- pool.shutdown #=> :shuttingdown
111
- pool.status #=> []
112
- pool.wait_for_termination
113
-
114
- pool.size #=> 0
115
- pool.status #=> []
116
- pool.shutdown? #=> true
117
- ```
118
-
119
- ## Global Thread Pool
120
-
121
- For efficiency, of the aforementioned concurrency methods (agents, futures, promises, and
122
- goroutines) run against a global thread pool. This pool can be directly accessed through the
123
- `$GLOBAL_THREAD_POOL` global variable. Generally, this pool should not be directly accessed.
124
- Use the other concurrency features instead.
125
-
126
- By default the global thread pool is a `NullThreadPool`. This isn't a real thread pool at all.
127
- It's simply a proxy for creating new threads on every post to the pool. I couldn't decide which
128
- of the other threads pools and what configuration would be the most universally appropriate so
129
- I punted. If you understand thread pools then you know enough to make your own choice. That's
130
- why the global thread pool can be changed.
131
-
132
- ### Changing the Global Thread Pool
133
-
134
- It is possible to change the global thread pool. Simply assign a new pool to the `$GLOBAL_THREAD_POOL`
135
- variable:
136
-
137
- ```ruby
138
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
139
- ```
140
-
141
- Ideally this should be done at application startup, before any concurrency functions are called.
142
- If the circumstances warrant the global thread pool can be changed at runtime. Just make sure to
143
- shutdown the old global thread pool so that no tasks are lost:
144
-
145
- ```ruby
146
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
147
-
148
- # do stuff...
149
-
150
- old_global_pool = $GLOBAL_THREAD_POOL
151
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
152
- old_global_pool.shutdown
153
- ```
154
-
155
- ### NullThreadPool
156
-
157
- If for some reason an appliction would be better served by *not* having a global thread pool, the
158
- `NullThreadPool` is provided. The `NullThreadPool` is compatible with the global thread pool but
159
- it is not an actual thread pool. Instead it spawns a new thread on every call to the `post` method.
160
-
161
- ### EventMachine
162
-
163
- The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
164
- is an awesome library for creating evented applications. EventMachine provides its own thread pool
165
- and the authors recommend using their pool rather than using Ruby's `Thread`. No sweat,
166
- `concurrent-ruby` is fully compatible with EventMachine. Simple require `eventmachine`
167
- *before* requiring `concurrent-ruby` then replace the global thread pool with an instance
168
- of `EventMachineDeferProxy`:
169
-
170
- ```ruby
171
- require 'eventmachine' # do this FIRST
172
- require 'concurrent'
173
-
174
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
175
- ```
176
-
177
- ## Per-class Thread Pools
178
-
179
- Many of the classes in this library use the global thread pool rather than creating new threads.
180
- Classes such as `Agent`, `Defer`, and others follow this pattern. There may be cases where a
181
- program would be better suited for one or more of these classes used a different thread pool.
182
- All classes that use the global thread pool support a class-level `thread_pool` attribute accessor.
183
- This property defaults to the global thread pool but can be changed at any time. Once changed, all
184
- new instances of that class will use the new thread pool.
185
-
186
- ```ruby
187
- Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> true
188
-
189
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10) #=> #<Concurrent::FixedThreadPool:0x007fe31130f1f0 ...
190
-
191
- Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> false
192
-
193
- Concurrent::Defer.thread_pool = Concurrent::CachedThreadPool.new #=> #<Concurrent::CachedThreadPool:0x007fef1c6b6b48 ...
194
- Concurrent::Defer.thread_pool == Concurrent::Agent.thread_pool #=> false
195
- Concurrent::Defer.thread_pool == $GLOBAL_THREAD_POOL #=> false
196
- ```
197
-
198
- ## Copyright
199
-
200
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
201
- It is free software and may be redistributed under the terms specified in the LICENSE file.
202
-
203
- ## License
204
-
205
- Released under the MIT license.
206
-
207
- http://www.opensource.org/licenses/mit-license.php
208
-
209
- > Permission is hereby granted, free of charge, to any person obtaining a copy
210
- > of this software and associated documentation files (the "Software"), to deal
211
- > in the Software without restriction, including without limitation the rights
212
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
213
- > copies of the Software, and to permit persons to whom the Software is
214
- > furnished to do so, subject to the following conditions:
215
- >
216
- > The above copyright notice and this permission notice shall be included in
217
- > all copies or substantial portions of the Software.
218
- >
219
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
220
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
221
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
222
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
223
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
224
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
225
- > THE SOFTWARE.