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,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.