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,61 @@
1
+ require 'functional/behavior'
2
+ require 'concurrent/event'
3
+
4
+ behavior_info(:thread_pool,
5
+ running?: 0,
6
+ shutdown?: 0,
7
+ killed?: 0,
8
+ shutdown: 0,
9
+ kill: 0,
10
+ size: 0,
11
+ wait_for_termination: -1,
12
+ post: -1,
13
+ :<< => 1,
14
+ status: 0)
15
+
16
+ behavior_info(:global_thread_pool,
17
+ post: -1,
18
+ :<< => 1)
19
+
20
+ module Concurrent
21
+
22
+ class ThreadPool
23
+
24
+ def initialize
25
+ @status = :running
26
+ @queue = Queue.new
27
+ @termination = Event.new
28
+ @pool = []
29
+ end
30
+
31
+ def running?
32
+ return @status == :running
33
+ end
34
+
35
+ def shutdown?
36
+ return ! running?
37
+ end
38
+
39
+ def killed?
40
+ return @status == :killed
41
+ end
42
+
43
+ def shutdown
44
+ @pool.size.times{ @queue << :stop }
45
+ @status = :shuttingdown
46
+ end
47
+
48
+ def wait_for_termination(timeout = nil)
49
+ if shutdown? || killed?
50
+ return true
51
+ else
52
+ return @termination.wait(timeout)
53
+ end
54
+ end
55
+
56
+ def <<(block)
57
+ self.post(&block)
58
+ return self
59
+ end
60
+ end
61
+ end
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1 @@
1
+ require 'concurrent'
@@ -0,0 +1,123 @@
1
+ # Secret Agent Man
2
+
3
+ Agents are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) keyword.
4
+ An agent is a single atomic value that represents an identity. The current value
5
+ of the agent can be requested at any time (`deref`). Each agent has a work queue and operates on
6
+ the global thread pool (see below). Consumers can `post` code blocks to the
7
+ agent. The code block (function) will receive the current value of the agent as its sole
8
+ parameter. The return value of the block will become the new value of the agent. Agents support
9
+ two error handling modes: fail and continue. A good example of an agent is a shared incrementing
10
+ counter, such as the score in a video game.
11
+
12
+ An agent must be initialize with an initial value. This value is always accessible via the `value`
13
+ (or `deref`) methods. Code blocks sent to the agent will be processed in the order received. As
14
+ each block is processed the current value is updated with the result from the block. This update
15
+ is an atomic operation so a `deref` will never block and will always return the current value.
16
+
17
+ When an agent is created it may be given an optional `validate` block and zero or more `rescue`
18
+ blocks. When a new value is calculated the value will be checked against the validator, if present.
19
+ If the validator returns `true` the new value will be accepted. If it returns `false` it will be
20
+ rejected. If a block raises an exception during execution the list of `rescue` blocks will be
21
+ seacrhed in order until one matching the current exception is found. That `rescue` block will
22
+ then be called an passed the exception object. If no matching `rescue` block is found, or none
23
+ were configured, then the exception will be suppressed.
24
+
25
+ Agents also implement Ruby's [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html).
26
+ Code that observes an agent will receive a callback with the new value any time the value
27
+ is changed.
28
+
29
+ ## Examples
30
+
31
+ A simple example:
32
+
33
+ ```ruby
34
+ require 'concurrent'
35
+
36
+ score = Concurrent::Agent.new(10)
37
+ score.value #=> 10
38
+
39
+ score << proc{|current| current + 100 }
40
+ sleep(0.1)
41
+ score.value #=> 110
42
+
43
+ score << proc{|current| current * 2 }
44
+ sleep(0.1)
45
+ deref score #=> 220
46
+
47
+ score << proc{|current| current - 50 }
48
+ sleep(0.1)
49
+ score.value #=> 170
50
+ ```
51
+
52
+ With validation and error handling:
53
+
54
+ ```ruby
55
+ score = agent(0).validate{|value| value <= 1024 }.
56
+ rescue(NoMethodError){|ex| puts "Bam!" }.
57
+ rescue(ArgumentError){|ex| puts "Pow!" }.
58
+ rescue{|ex| puts "Boom!" }
59
+ score.value #=> 0
60
+
61
+ score << proc{|current| current + 2048 }
62
+ sleep(0.1)
63
+ score.value #=> 0
64
+
65
+ score << proc{|current| raise ArgumentError }
66
+ sleep(0.1)
67
+ #=> puts "Pow!"
68
+ score.value #=> 0
69
+
70
+ score << proc{|current| current + 100 }
71
+ sleep(0.1)
72
+ score.value #=> 100
73
+ ```
74
+
75
+ With observation:
76
+
77
+ ```ruby
78
+ bingo = Class.new{
79
+ def update(time, score)
80
+ puts "Bingo! [score: #{score}, time: #{time}]" if score >= 100
81
+ end
82
+ }.new
83
+
84
+ score = agent(0)
85
+ score.add_observer(bingo)
86
+
87
+ score << proc{|current| sleep(0.1); current += 30 }
88
+ score << proc{|current| sleep(0.1); current += 30 }
89
+ score << proc{|current| sleep(0.1); current += 30 }
90
+ score << proc{|current| sleep(0.1); current += 30 }
91
+
92
+ sleep(1)
93
+ #=> Bingo! [score: 120, time: 2013-07-22 21:26:08 -0400]
94
+ ```
95
+
96
+ ## Copyright
97
+
98
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
99
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
100
+
101
+ ## License
102
+
103
+ Released under the MIT license.
104
+
105
+ http://www.opensource.org/licenses/mit-license.php
106
+
107
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
108
+ > of this software and associated documentation files (the "Software"), to deal
109
+ > in the Software without restriction, including without limitation the rights
110
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
111
+ > copies of the Software, and to permit persons to whom the Software is
112
+ > furnished to do so, subject to the following conditions:
113
+ >
114
+ > The above copyright notice and this permission notice shall be included in
115
+ > all copies or substantial portions of the Software.
116
+ >
117
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
118
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
119
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
120
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
121
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
122
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
123
+ > THE SOFTWARE.
@@ -0,0 +1,174 @@
1
+ # I Can't Think of a Movie or Music Reference for Defer
2
+
3
+ In the pantheon of concurrency objects a `Defer` sits somewhere between `Future` and `Promise`.
4
+ Inspired by [EventMachine's *defer* method](https://github.com/eventmachine/eventmachine/wiki/EM::Deferrable-and-EM.defer),
5
+ a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`.
6
+
7
+ Unlike `Future` and `Promise` a defer is non-blocking. The deferred *operation* is performed on another
8
+ thread. If the *operation* is successful an optional *callback* is called on the same thread as the *operation*.
9
+ The result of the *operation* is passed to the *callbacl*. If the *operation* fails (by raising an exception)
10
+ then an optional *errorback* (error callback) is called on
11
+ the same thread as the *operation*. The raised exception is passed to the *errorback*. The calling thread is
12
+ never aware of the result of the *operation*. This approach fits much more cleanly within an
13
+ [event-driven](http://en.wikipedia.org/wiki/Event-driven_programming) application.
14
+
15
+ The operation of a `Defer` can easily be simulated using either `Future` or `Promise` and traditional branching
16
+ (if/then/else) logic. This approach works but it is more verbose and partitions the work across two threads.
17
+ Whenever you find yourself checking the result of a `Future` or a `Promise` then branching based on the result,
18
+ consider a `Defer` instead.
19
+
20
+ For programmer convenience there are two syntaxes for creating and running a `Defer`. One is idiomatic of Ruby
21
+ and uses chained method calls. The other is more isiomatic of [functional programming](http://en.wikipedia.org/wiki/Concurrentprogramming)
22
+ and passes one or more `proc` objects as arguments. Do not mix syntaxes on a single `Defer` invocation.
23
+
24
+ ## Examples
25
+
26
+ A simple `Defer` using idiomatic Ruby syntax:
27
+
28
+ ```ruby
29
+ require 'concurrent'
30
+
31
+ deferred = Concurrent::Defer.new{ puts 'w00t!' }
32
+ # when using idiomatic syntax the #go method must be called
33
+ deferred.go
34
+ sleep(0.1)
35
+
36
+ #=> 'w00t!'
37
+ ```
38
+
39
+ A simple `Defer` using functional programming syntax:
40
+
41
+ ```ruby
42
+ operation = proc{ puts 'w00t!' }
43
+ Concurrent::Defer.new(operation) # NOTE: a call to #go is unnecessary
44
+ sleep(0.1)
45
+
46
+ #=> 'w00t!'
47
+
48
+ defer(operation)
49
+ sleep(0.1)
50
+
51
+ #=> 'w00t!'
52
+ ```
53
+
54
+ Adding a *callback*:
55
+
56
+ ```ruby
57
+ Concurrent::Defer.new{ "Jerry D'Antonio" }.
58
+ then{|result| puts "Hello, #{result}!" }.
59
+ go
60
+
61
+ #=> Hello, Jerry D'Antonio!
62
+
63
+ operation = proc{ "Jerry D'Antonio" }
64
+ callback = proc{|result| puts "Hello, #{result}!" }
65
+ defer(operation, callback, nil)
66
+ sleep(0.1)
67
+
68
+ #=> Hello, Jerry D'Antonio!
69
+ ```
70
+
71
+ Adding an *errorback*:
72
+
73
+ ```ruby
74
+ Concurrent::Defer.new{ raise StandardError.new('Boom!') }.
75
+ rescue{|ex| puts ex.message }.
76
+ go
77
+ sleep(0.1)
78
+
79
+ #=> "Boom!"
80
+
81
+ operation = proc{ raise StandardError.new('Boom!') }
82
+ errorback = proc{|ex| puts ex.message }
83
+ defer(operation, nil, errorback)
84
+
85
+ #=> "Boom!"
86
+ ```
87
+
88
+ Putting it all together:
89
+
90
+ ```ruby
91
+ Concurrent::Defer.new{ "Jerry D'Antonio" }.
92
+ then{|result| puts "Hello, #{result}!" }.
93
+ rescue{|ex| puts ex.message }.
94
+ go
95
+
96
+ #=> Hello, Jerry D'Antonio!
97
+
98
+ operation = proc{ raise StandardError.new('Boom!') }
99
+ callback = proc{|result| puts result }
100
+ errorback = proc{|ex| puts ex.message }
101
+ defer(operation, callback, errorback)
102
+ sleep(0.1)
103
+
104
+ #=> "Boom!"
105
+ ```
106
+
107
+ Crossing the streams:
108
+
109
+ ```ruby
110
+ operation = proc{ true }
111
+ callback = proc{|result| puts result }
112
+ errorback = proc{|ex| puts ex.message }
113
+
114
+ Concurrent::Defer.new(operation, nil, nil){ false }
115
+ #=> ArgumentError: two operations given
116
+
117
+ defer(nil, callback, errorback)
118
+ # => ArgumentError: no operation given
119
+
120
+ Concurrent::Defer.new.go
121
+ # => ArgumentError: no operation given
122
+
123
+ defer(nil, nil, nil)
124
+ # => ArgumentError: no operation given
125
+
126
+ Concurrent::Defer.new(operation, nil, nil).
127
+ then{|result| puts result }.
128
+ go
129
+ #=> Concurrent::IllegalMethodCallError: the defer is already running
130
+
131
+ defer(callback, nil, nil).then{|result| puts result }
132
+ #=> Concurrent::IllegalMethodCallError: the defer is already running
133
+
134
+ Concurrent::Defer.new{ true }.
135
+ then{|result| puts "Boom!" }.
136
+ then{|result| puts "Bam!" }.
137
+ go
138
+ #=> Concurrent::IllegalMethodCallError: a callback has already been provided
139
+
140
+ Concurrent::Defer.new{ raise StandardError }.
141
+ rescue{|ex| puts "Boom!" }.
142
+ rescue{|ex| puts "Bam!" }.
143
+ go
144
+ #=> Concurrent::IllegalMethodCallError: a errorback has already been provided
145
+ ```
146
+
147
+ ## Copyright
148
+
149
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
150
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
151
+
152
+ ## License
153
+
154
+ Released under the MIT license.
155
+
156
+ http://www.opensource.org/licenses/mit-license.php
157
+
158
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
159
+ > of this software and associated documentation files (the "Software"), to deal
160
+ > in the Software without restriction, including without limitation the rights
161
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
162
+ > copies of the Software, and to permit persons to whom the Software is
163
+ > furnished to do so, subject to the following conditions:
164
+ >
165
+ > The above copyright notice and this permission notice shall be included in
166
+ > all copies or substantial portions of the Software.
167
+ >
168
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
169
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
170
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
171
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
172
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
173
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
174
+ > THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Event
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Concurrent Ruby* is Copyright &copy; 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,83 @@
1
+ # We're Sending You Back to the Future!
2
+
3
+ Futures are inspired by [Clojure's](http://clojure.org/) [future](http://clojuredocs.org/clojure_core/clojure.core/future) keyword.
4
+ A future represents a promise to complete an action at some time in the future. The action is atomic and permanent.
5
+ The idea behind a future is to send an action off for asynchronous operation, do other stuff, then return and
6
+ retrieve the result of the async operation at a later time. Futures run on the global thread pool (see below).
7
+
8
+ Futures have three possible states: *pending*, *rejected*, and *fulfilled*. When a future is created it is set
9
+ to *pending* and will remain in that state until processing is complete. A completed future is either *rejected*,
10
+ indicating that an exception was thrown during processing, or *fulfilled*, indicating succedd. If a future is
11
+ *fulfilled* its `value` will be updated to reflect the result of the operation. If *rejected* the `reason` will
12
+ be updated with a reference to the thrown exception. The predicate methods `pending?`, `rejected`, and `fulfilled?`
13
+ can be called at any time to obtain the state of the future, as can the `state` method, which returns a symbol.
14
+
15
+ Retrieving the value of a future is done through the `value` (alias: `deref`) method. Obtaining the value of
16
+ a future is a potentially blocking operation. When a future is *rejected* a call to `value` will return `nil`
17
+ immediately. When a future is *fulfilled* a call to `value` will immediately return the current value.
18
+ When a future is *pending* a call to `value` will block until the future is either *rejected* or *fulfilled*.
19
+ A *timeout* value can be passed to `value` to limit how long the call will block. If `nil` the call will
20
+ block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
21
+ maximum number of seconds to block.
22
+
23
+ ## Examples
24
+
25
+ A fulfilled example:
26
+
27
+ ```ruby
28
+ require 'concurrent'
29
+
30
+ count = Concurrent::Future{ sleep(10); 10 }
31
+ count.state #=> :pending
32
+ count.pending? #=> true
33
+
34
+ # do stuff...
35
+
36
+ count.value(0) #=> nil (does not block)
37
+
38
+ count.value #=> 10 (after blocking)
39
+ count.state #=> :fulfilled
40
+ count.fulfilled? #=> true
41
+ deref count #=> 10
42
+ ```
43
+
44
+ A rejected example:
45
+
46
+ ```ruby
47
+ count = future{ sleep(10); raise StandardError.new("Boom!") }
48
+ count.state #=> :pending
49
+ pending?(count) #=> true
50
+
51
+ deref(count) #=> nil (after blocking)
52
+ rejected?(count) #=> true
53
+ count.reason #=> #<StandardError: Boom!>
54
+ ```
55
+
56
+ ## Copyright
57
+
58
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
59
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
60
+
61
+ ## License
62
+
63
+ Released under the MIT license.
64
+
65
+ http://www.opensource.org/licenses/mit-license.php
66
+
67
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
68
+ > of this software and associated documentation files (the "Software"), to deal
69
+ > in the Software without restriction, including without limitation the rights
70
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
71
+ > copies of the Software, and to permit persons to whom the Software is
72
+ > furnished to do so, subject to the following conditions:
73
+ >
74
+ > The above copyright notice and this permission notice shall be included in
75
+ > all copies or substantial portions of the Software.
76
+ >
77
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
78
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
79
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
80
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
81
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
82
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
83
+ > THE SOFTWARE.