concurrent-ruby 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ # Go, Go, Gadget Goroutine!
2
+
3
+ A goroutine is the simplest of the concurrency utilities in this library. It is inspired by
4
+ [Go's](http://golang.org/) [goroutines](https://gobyexample.com/goroutines) and
5
+ [Erlang's](http://www.erlang.org/) [spawn](http://erlangexamples.com/tag/spawn/) keyword. The
6
+ `go` function is nothing more than a simple way to send a block to the global thread pool (see below)
7
+ for processing.
8
+
9
+ ## Examples
10
+
11
+ ```ruby
12
+ require 'concurrent'
13
+
14
+ @expected = nil
15
+
16
+ go(1, 2, 3){|a, b, c| sleep(1); @expected = [c, b, a] }
17
+
18
+ sleep(0.1)
19
+ @expected #=> nil
20
+
21
+ sleep(2)
22
+ @expected #=> [3, 2, 1]
23
+ ```
24
+
25
+ ## Copyright
26
+
27
+ *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
28
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
29
+
30
+ ## License
31
+
32
+ Released under the MIT license.
33
+
34
+ http://www.opensource.org/licenses/mit-license.php
35
+
36
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
37
+ > of this software and associated documentation files (the "Software"), to deal
38
+ > in the Software without restriction, including without limitation the rights
39
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40
+ > copies of the Software, and to permit persons to whom the Software is
41
+ > furnished to do so, subject to the following conditions:
42
+ >
43
+ > The above copyright notice and this permission notice shall be included in
44
+ > all copies or substantial portions of the Software.
45
+ >
46
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
52
+ > THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Obligation
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -0,0 +1,225 @@
1
+ # Promises, Promises...
2
+
3
+ A promise is the most powerful and versatile of the concurrency objects in this library.
4
+ Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
5
+ and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications.
6
+
7
+ > A promise represents the eventual value returned from the single completion of an operation.
8
+
9
+ Promises are similar to futures and share many of the same behaviours. Promises are far more robust,
10
+ however. Promises can be chained in a tree structure where each promise may have zero or more children.
11
+ Promises are chained using the `then` method. The result of a call to `then` is always another promise.
12
+ Promises are resolved asynchronously in the order they are added to the tree. Parents are guaranteed
13
+ to be resolved before their children. The result of each promise is passed to each of its children
14
+ upon resolution. When a promise is rejected all its children will be summarily rejected.
15
+
16
+ Promises have three possible states: *pending*, *rejected*, and *fulfilled*. When a promise is created it is set
17
+ to *pending* and will remain in that state until processing is complete. A completed promise is either *rejected*,
18
+ indicating that an exception was thrown during processing, or *fulfilled*, indicating succedd. If a promise is
19
+ *fulfilled* its `value` will be updated to reflect the result of the operation. If *rejected* the `reason` will
20
+ be updated with a reference to the thrown exception. The predicate methods `pending?`, `rejected`, and `fulfilled?`
21
+ can be called at any time to obtain the state of the promise, as can the `state` method, which returns a symbol.
22
+
23
+ Retrieving the value of a promise is done through the `value` (alias: `deref`) method. Obtaining the value of
24
+ a promise is a potentially blocking operation. When a promise is *rejected* a call to `value` will return `nil`
25
+ immediately. When a promise is *fulfilled* a call to `value` will immediately return the current value.
26
+ When a promise is *pending* a call to `value` will block until the promise is either *rejected* or *fulfilled*.
27
+ A *timeout* value can be passed to `value` to limit how long the call will block. If `nil` the call will
28
+ block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
29
+ maximum number of seconds to block.
30
+
31
+ ## Examples
32
+
33
+ Start by requiring promises
34
+
35
+ ```ruby
36
+ require 'concurrent'
37
+ ```
38
+
39
+ Then create one
40
+
41
+ ```ruby
42
+ p = Promise.new("Jerry", "D'Antonio") do |first, last|
43
+ "#{last}, #{first}"
44
+ end
45
+
46
+ # -or-
47
+
48
+ p = promise(10){|x| x * x * x }
49
+ ```
50
+
51
+ Promises can be chained using the `then` method. The `then` method
52
+ accepts a block but no arguments. The result of the each promise is
53
+ passed as the block argument to chained promises
54
+
55
+ ```ruby
56
+ p = promise(10){|x| x * 2}.then{|result| result - 10 }
57
+ ```
58
+
59
+ And so on, and so on, and so on...
60
+
61
+ ```ruby
62
+ p = promise(10){|x| x * 2}.
63
+ then{|result| result - 10 }.
64
+ then{|result| result * 3 }.
65
+ then{|result| result % 5 }
66
+ ```
67
+
68
+ Promises are executed asynchronously so a newly-created promise *should* always be in the pending state
69
+
70
+
71
+ ```ruby
72
+ p = promise{ "Hello, world!" }
73
+ p.state #=> :pending
74
+ p.pending? #=> true
75
+ ```
76
+
77
+ Wait a little bit, and the promise will resolve and provide a value
78
+
79
+ ```ruby
80
+ p = promise{ "Hello, world!" }
81
+ sleep(0.1)
82
+
83
+ p.state #=> :fulfilled
84
+ p.fulfilled? #=> true
85
+
86
+ p.value #=> "Hello, world!"
87
+
88
+ ```
89
+
90
+ If an exception occurs, the promise will be rejected and will provide
91
+ a reason for the rejection
92
+
93
+ ```ruby
94
+ p = promise{ raise StandardError.new("Here comes the Boom!") }
95
+ sleep(0.1)
96
+
97
+ p.state #=> :rejected
98
+ p.rejected? #=> true
99
+
100
+ p.reason=> #=> "#<StandardError: Here comes the Boom!>"
101
+ ```
102
+
103
+ ### Rejection
104
+
105
+ Much like the economy, rejection exhibits a trickle-down effect. When
106
+ a promise is rejected all its children will be rejected
107
+
108
+ ```ruby
109
+ p = [ promise{ Thread.pass; raise StandardError } ]
110
+
111
+ 10.times{|i| p << p.first.then{ i } }
112
+ sleep(0.1)
113
+
114
+ p.length #=> 11
115
+ p.first.state #=> :rejected
116
+ p.last.state #=> :rejected
117
+ ```
118
+
119
+ Once a promise is rejected it will not accept any children. Calls
120
+ to `then` will continually return `self`
121
+
122
+ ```ruby
123
+ p = promise{ raise StandardError }
124
+ sleep(0.1)
125
+
126
+ p.object_id #=> 32960556
127
+ p.then{}.object_id #=> 32960556
128
+ p.then{}.object_id #=> 32960556
129
+ ```
130
+
131
+ ### Error Handling
132
+
133
+ Promises support error handling callbacks is a style mimicing Ruby's
134
+ own exception handling mechanism, namely `rescue`
135
+
136
+
137
+ ```ruby
138
+ promise{ "dangerous operation..." }.rescue{|ex| puts "Bam!" }
139
+
140
+ # -or- (for the Java/C# crowd)
141
+ promise{ "dangerous operation..." }.catch{|ex| puts "Boom!" }
142
+
143
+ # -or- (for the hipsters)
144
+ promise{ "dangerous operation..." }.on_error{|ex| puts "Pow!" }
145
+ ```
146
+
147
+ As with Ruby's `rescue` mechanism, a promise's `rescue` method can
148
+ accept an optional Exception class argument (defaults to `Exception`
149
+ when not specified)
150
+
151
+
152
+ ```ruby
153
+ promise{ "dangerous operation..." }.rescue(ArgumentError){|ex| puts "Bam!" }
154
+ ```
155
+
156
+ Calls to `rescue` can also be chained
157
+
158
+ ```ruby
159
+ promise{ "dangerous operation..." }.
160
+ rescue(ArgumentError){|ex| puts "Bam!" }.
161
+ rescue(NoMethodError){|ex| puts "Boom!" }.
162
+ rescue(StandardError){|ex| puts "Pow!" }
163
+ ```
164
+
165
+ When there are multiple `rescue` handlers the first one to match the thrown
166
+ exception will be triggered
167
+
168
+ ```ruby
169
+ promise{ raise NoMethodError }.
170
+ rescue(ArgumentError){|ex| puts "Bam!" }.
171
+ rescue(NoMethodError){|ex| puts "Boom!" }.
172
+ rescue(StandardError){|ex| puts "Pow!" }
173
+
174
+ sleep(0.1)
175
+
176
+ #=> Boom!
177
+ ```
178
+
179
+ Trickle-down rejection also applies to rescue handlers. When a promise is rejected,
180
+ for any reason, its rescue handlers will be triggered. Rejection of the parent counts.
181
+
182
+ ```ruby
183
+ promise{ Thread.pass; raise StandardError }.
184
+ then{ true }.rescue{ puts 'Boom!' }.
185
+ then{ true }.rescue{ puts 'Boom!' }.
186
+ then{ true }.rescue{ puts 'Boom!' }.
187
+ then{ true }.rescue{ puts 'Boom!' }.
188
+ then{ true }.rescue{ puts 'Boom!' }
189
+ sleep(0.1)
190
+
191
+ #=> Boom!
192
+ #=> Boom!
193
+ #=> Boom!
194
+ #=> Boom!
195
+ #=> Boom!
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.
@@ -0,0 +1,197 @@
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 'functional/cached_thread_pool'
91
+ # or
92
+ require 'functional/concurrency'
93
+
94
+ pool = Concurrent::CachedThreadPool.new
95
+
96
+ pool.size #=> 0
97
+ pool.running? #=> true
98
+ pool.status #=> []
99
+
100
+ pool.post(1,2,3){|*args| sleep(10) }
101
+ pool << proc{ sleep(10) }
102
+ pool.size #=> 2
103
+ pool.status #=> [[:working, nil, "sleep"], [:working, nil, "sleep"]]
104
+
105
+ sleep(11)
106
+ pool.status #=> [[:idle, 23, "sleep"], [:idle, 23, "sleep"]]
107
+
108
+ sleep(60)
109
+ pool.size #=> 0
110
+ pool.status #=> []
111
+
112
+ pool.shutdown #=> :shuttingdown
113
+ pool.status #=> []
114
+ pool.wait_for_termination
115
+
116
+ pool.size #=> 0
117
+ pool.status #=> []
118
+ pool.shutdown? #=> true
119
+ ```
120
+
121
+ ## Global Thread Pool
122
+
123
+ For efficiency, of the aforementioned concurrency methods (agents, futures, promises, and
124
+ goroutines) run against a global thread pool. This pool can be directly accessed through the
125
+ `$GLOBAL_THREAD_POOL` global variable. Generally, this pool should not be directly accessed.
126
+ Use the other concurrency features instead.
127
+
128
+ By default the global thread pool is a `CachedThreadPool`. This means it consumes no resources
129
+ unless concurrency functions are called. Most of the time this pool can simply be left alone.
130
+
131
+ ### Changing the Global Thread Pool
132
+
133
+ It is possible to change the global thread pool. Simply assign a new pool to the `$GLOBAL_THREAD_POOL`
134
+ variable:
135
+
136
+ ```ruby
137
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
138
+ ```
139
+
140
+ Ideally this should be done at application startup, before any concurrency functions are called.
141
+ If the circumstances warrant the global thread pool can be changed at runtime. Just make sure to
142
+ shutdown the old global thread pool so that no tasks are lost:
143
+
144
+ ```ruby
145
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
146
+
147
+ # do stuff...
148
+
149
+ old_global_pool = $GLOBAL_THREAD_POOL
150
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
151
+ old_global_pool.shutdown
152
+ ```
153
+
154
+ ### EventMachine
155
+
156
+ The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
157
+ is an awesome library for creating evented applications. EventMachine provides its own thread pool
158
+ and the authors recommend using their pool rather than using Ruby's `Thread`. No sweat,
159
+ `functional-ruby` is fully compatible with EventMachine. Simple require `eventmachine`
160
+ *before* requiring `functional-ruby` then replace the global thread pool with an instance
161
+ of `EventMachineDeferProxy`:
162
+
163
+ ```ruby
164
+ require 'eventmachine' # do this FIRST
165
+ require 'functional/concurrency'
166
+
167
+ $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
168
+ ```
169
+
170
+ ## Copyright
171
+
172
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
173
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
174
+
175
+ ## License
176
+
177
+ Released under the MIT license.
178
+
179
+ http://www.opensource.org/licenses/mit-license.php
180
+
181
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
182
+ > of this software and associated documentation files (the "Software"), to deal
183
+ > in the Software without restriction, including without limitation the rights
184
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
185
+ > copies of the Software, and to permit persons to whom the Software is
186
+ > furnished to do so, subject to the following conditions:
187
+ >
188
+ > The above copyright notice and this permission notice shall be included in
189
+ > all copies or substantial portions of the Software.
190
+ >
191
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
192
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
193
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
194
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
195
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
196
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
197
+ > THE SOFTWARE.