concurrent-ruby 0.2.2 → 0.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -42
  3. data/lib/concurrent.rb +5 -6
  4. data/lib/concurrent/agent.rb +29 -33
  5. data/lib/concurrent/cached_thread_pool.rb +26 -105
  6. data/lib/concurrent/channel.rb +94 -0
  7. data/lib/concurrent/event.rb +8 -17
  8. data/lib/concurrent/executor.rb +68 -72
  9. data/lib/concurrent/fixed_thread_pool.rb +15 -83
  10. data/lib/concurrent/functions.rb +7 -22
  11. data/lib/concurrent/future.rb +29 -9
  12. data/lib/concurrent/null_thread_pool.rb +5 -2
  13. data/lib/concurrent/obligation.rb +6 -16
  14. data/lib/concurrent/promise.rb +9 -10
  15. data/lib/concurrent/runnable.rb +103 -0
  16. data/lib/concurrent/supervisor.rb +271 -44
  17. data/lib/concurrent/thread_pool.rb +112 -39
  18. data/lib/concurrent/version.rb +1 -1
  19. data/md/executor.md +9 -3
  20. data/md/goroutine.md +11 -9
  21. data/md/reactor.md +32 -0
  22. data/md/supervisor.md +43 -0
  23. data/spec/concurrent/agent_spec.rb +128 -51
  24. data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
  25. data/spec/concurrent/channel_spec.rb +446 -0
  26. data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
  27. data/spec/concurrent/event_spec.rb +0 -19
  28. data/spec/concurrent/executor_spec.rb +167 -119
  29. data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
  30. data/spec/concurrent/functions_spec.rb +0 -20
  31. data/spec/concurrent/future_spec.rb +88 -0
  32. data/spec/concurrent/null_thread_pool_spec.rb +23 -2
  33. data/spec/concurrent/obligation_shared.rb +0 -5
  34. data/spec/concurrent/promise_spec.rb +9 -10
  35. data/spec/concurrent/runnable_shared.rb +62 -0
  36. data/spec/concurrent/runnable_spec.rb +233 -0
  37. data/spec/concurrent/supervisor_spec.rb +912 -47
  38. data/spec/concurrent/thread_pool_shared.rb +18 -31
  39. data/spec/spec_helper.rb +10 -3
  40. metadata +17 -23
  41. data/lib/concurrent/defer.rb +0 -65
  42. data/lib/concurrent/reactor.rb +0 -166
  43. data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
  44. data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
  45. data/lib/concurrent/utilities.rb +0 -32
  46. data/md/defer.md +0 -174
  47. data/spec/concurrent/defer_spec.rb +0 -199
  48. data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
  49. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
  50. data/spec/concurrent/reactor_spec.rb +0 -364
  51. data/spec/concurrent/utilities_spec.rb +0 -74
@@ -1,83 +0,0 @@
1
- require 'drb/drb'
2
- require 'drb/acl'
3
- require 'functional'
4
- require 'concurrent/reactor'
5
- require 'concurrent/supervisor'
6
-
7
- module Concurrent
8
- class Reactor
9
-
10
- class DRbAsyncDemux
11
-
12
- behavior(:async_event_demux)
13
-
14
- DEFAULT_URI = 'druby://localhost:12345'
15
- DEFAULT_ACL = %w{deny all allow 127.0.0.1}
16
-
17
- attr_reader :uri
18
- attr_reader :acl
19
-
20
- def initialize(opts = {})
21
- @uri = opts[:uri] || DEFAULT_URI
22
- @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
23
- end
24
-
25
- def set_reactor(reactor)
26
- raise ArgumentError.new('invalid reactor') unless reactor.behaves_as?(:demux_reactor)
27
- @reactor = reactor
28
- end
29
-
30
- def run
31
- raise StandardError.new('already running') if running?
32
- DRb.install_acl(@acl)
33
- @service = DRb.start_service(@uri, Demultiplexer.new(@reactor))
34
- end
35
-
36
- def stop
37
- @service = DRb.stop_service
38
- end
39
-
40
- def running?
41
- return ! @service.nil?
42
- end
43
-
44
- private
45
-
46
- class Demultiplexer
47
-
48
- def initialize(reactor)
49
- @reactor = reactor
50
- end
51
-
52
- Concurrent::Reactor::RESERVED_EVENTS.each do |event|
53
- define_method(event){|*args| false }
54
- end
55
-
56
- def method_missing(method, *args, &block)
57
- (class << self; self; end).class_eval do
58
- define_method(method) do |*args|
59
- begin
60
- result = @reactor.handle(method, *args)
61
- rescue Exception => ex
62
- raise DRb::DRbRemoteError.new(ex)
63
- end
64
- case result.first
65
- when :ok
66
- return result.last
67
- when :ex
68
- raise result.last
69
- when :noop
70
- raise NoMethodError.new("undefined method '#{method}' for #{self}")
71
- else
72
- raise DRb::DRbError.new("unexpected response from method '#{method}'")
73
- end
74
- end
75
- end
76
- self.send(method, *args)
77
- end
78
- end
79
- end
80
-
81
- DRbAsyncDemultiplexer = DRbAsyncDemux
82
- end
83
- end
@@ -1,131 +0,0 @@
1
- require 'socket'
2
- require 'drb/acl'
3
- require 'functional'
4
- require 'concurrent/reactor'
5
- require 'concurrent/supervisor'
6
-
7
- module Concurrent
8
- class Reactor
9
-
10
- class TcpSyncDemux
11
-
12
- behavior(:sync_event_demux)
13
-
14
- DEFAULT_HOST = '127.0.0.1'
15
- DEFAULT_PORT = 12345
16
- DEFAULT_ACL = %w{deny all allow 127.0.0.1}
17
-
18
- attr_reader :host
19
- attr_reader :port
20
- attr_reader :acl
21
-
22
- def initialize(opts = {})
23
- @host = opts[:host] || DEFAULT_HOST
24
- @port = opts[:port] || DEFAULT_PORT
25
- @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
26
- end
27
-
28
- def run
29
- raise StandardError.new('already running') if running?
30
- begin
31
- @server = TCPServer.new(@host, @port)
32
- return true
33
- rescue Exception => ex
34
- return false
35
- end
36
- end
37
-
38
- def stop
39
- begin
40
- @socket.close unless @socket.nil?
41
- rescue Exception => ex
42
- # suppress
43
- end
44
-
45
- begin
46
- @server.close unless @server.nil?
47
- rescue Exception => ex
48
- # suppress
49
- end
50
-
51
- @server = @socket = nil
52
- return true
53
- end
54
-
55
- def reset
56
- stop
57
- sleep(1)
58
- run
59
- end
60
-
61
- def running?
62
- return ! @server.nil?
63
- end
64
-
65
- def accept
66
- @socket = @server.accept if @socket.nil?
67
- return nil unless @acl.allow_socket?(@socket)
68
- event, args = get_message(@socket)
69
- return nil if event.nil?
70
- return Reactor::EventContext.new(event, args)
71
- rescue Exception => ex
72
- reset
73
- return nil
74
- end
75
-
76
- def respond(result, message)
77
- return nil if @socket.nil?
78
- @socket.puts(format_message(result, message))
79
- rescue Exception => ex
80
- reset
81
- end
82
-
83
- def self.format_message(event, *args)
84
- event = event.to_s.strip
85
- raise ArgumentError.new('nil or empty event') if event.empty?
86
- args = args.reduce('') do |memo, arg|
87
- memo << "#{arg}\r\n"
88
- end
89
- return "#{event}\r\n#{args}\r\n"
90
- end
91
-
92
- def format_message(*args)
93
- self.class.format_message(*args)
94
- end
95
-
96
- def self.parse_message(message)
97
- message = message.lines.map(&:chomp) if message.is_a?(String)
98
- return [nil, []] if message.nil?
99
- event = message.first.match(/^:?(\w+)/)
100
- event = event[1].to_s.downcase.to_sym unless event.nil?
101
- args = message.slice(1, message.length) || []
102
- args.pop if args.last.nil? || args.last.empty?
103
- return [event, args]
104
- end
105
-
106
- def parse_message(*args)
107
- self.class.parse_message(*args)
108
- end
109
-
110
- def self.get_message(socket)
111
- message = []
112
- while line = socket.gets
113
- if line.nil? || (line = line.strip).empty?
114
- break
115
- else
116
- message << line
117
- end
118
- end
119
-
120
- if message.empty?
121
- return nil
122
- else
123
- return parse_message(message)
124
- end
125
- end
126
- def get_message(*args) self.class.get_message(*args); end
127
- end
128
-
129
- TcpSyncDemultiplexer = TcpSyncDemux
130
- end
131
- end
@@ -1,32 +0,0 @@
1
- module Kernel
2
-
3
- # Perform the given block as though it were an atomic operation. This means
4
- # that the Ruby scheduler cannot premept the block and context switch to
5
- # another thread. Basically a light wrapper around Ruby's Fiber class.
6
- #
7
- # @note Be very careful about what operations you perform within an atomic
8
- # block. Blocking operations such as I/O should *never* occur within an
9
- # atomic block. In those cases the entire Ruby VM will lock until the
10
- # blocking operation is complete. This would be bad.
11
- #
12
- # @yield calls the block
13
- # @yieldparam args an arbitrary set of block arguments
14
- #
15
- # @param [Array] zero more more optional arguments to pass to the block
16
- def atomic(*args)
17
- raise ArgumentError.new('no block given') unless block_given?
18
- return Fiber.new {
19
- yield(*args)
20
- }.resume
21
- end
22
- module_function :atomic
23
- end
24
-
25
- class Mutex
26
-
27
- def sync_with_timeout(timeout, &block)
28
- Timeout::timeout(timeout) {
29
- synchronize(&block)
30
- }
31
- end
32
- end
@@ -1,174 +0,0 @@
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`. Defers run on the global thread pool.
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 the same thread as the *operation*. The raised
11
- exception is passed to the *errorback*. The calling thread is never aware of the result of the *operation*.
12
- 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.
@@ -1,199 +0,0 @@
1
- require 'spec_helper'
2
- require_relative 'uses_global_thread_pool_shared'
3
-
4
- module Concurrent
5
-
6
- describe Defer do
7
-
8
- let!(:thread_pool_user){ Defer }
9
- it_should_behave_like Concurrent::UsesGlobalThreadPool
10
-
11
- before(:each) do
12
- Defer.thread_pool = FixedThreadPool.new(1)
13
- end
14
-
15
- context '#initialize' do
16
-
17
- it 'raises an exception if no block or operation given' do
18
- lambda {
19
- Defer.new
20
- }.should raise_error(ArgumentError)
21
- end
22
-
23
- it 'raises an exception if both a block and an operation given' do
24
- lambda {
25
- operation = proc{ nil }
26
- Defer.new(op: operation){ nil }
27
- }.should raise_error(ArgumentError)
28
- end
29
-
30
- it 'starts the thread if an operation is given' do
31
- Defer.thread_pool.should_receive(:post).once.with(any_args())
32
- operation = proc{ nil }
33
- Defer.new(op: operation)
34
- end
35
-
36
- it 'does not start the thread if neither a callback or errorback is given' do
37
- Defer.thread_pool.should_not_receive(:post)
38
- Defer.new{ nil }
39
- end
40
- end
41
-
42
- context '#then' do
43
-
44
- it 'raises an exception if no block given' do
45
- lambda {
46
- Defer.new{ nil }.then
47
- }.should raise_error(ArgumentError)
48
- end
49
-
50
- it 'raises an exception if called twice' do
51
- lambda {
52
- Defer.new{ nil }.then{|result| nil }.then{|result| nil }
53
- }.should raise_error(IllegalMethodCallError)
54
- end
55
-
56
- it 'raises an exception if an operation was provided at construction' do
57
- lambda {
58
- operation = proc{ nil }
59
- Defer.new(op: operation).then{|result| nil }
60
- }.should raise_error(IllegalMethodCallError)
61
- end
62
-
63
- it 'raises an exception if a callback was provided at construction' do
64
- lambda {
65
- callback = proc{|result|nil }
66
- Defer.new(callback: callback){ nil }.then{|result| nil }
67
- }.should raise_error(IllegalMethodCallError)
68
- end
69
-
70
- it 'returns self' do
71
- deferred = Defer.new{ nil }
72
- deferred.then{|result| nil }.should eq deferred
73
- end
74
- end
75
-
76
- context '#rescue' do
77
-
78
- it 'raises an exception if no block given' do
79
- lambda {
80
- Defer.new{ nil }.rescue
81
- }.should raise_error(ArgumentError)
82
- end
83
-
84
- it 'raises an exception if called twice' do
85
- lambda {
86
- Defer.new{ nil }.rescue{ nil }.rescue{ nil }
87
- }.should raise_error(IllegalMethodCallError)
88
- end
89
-
90
- it 'raises an exception if an operation was provided at construction' do
91
- lambda {
92
- operation = proc{ nil }
93
- Defer.new(op: operation).rescue{|ex| nil }
94
- }.should raise_error(IllegalMethodCallError)
95
- end
96
-
97
- it 'raises an exception if an errorback was provided at construction' do
98
- lambda {
99
- errorback = proc{|ex| nil }
100
- Defer.new(errorback: errorback){ nil }.rescue{|ex| nil }
101
- }.should raise_error(IllegalMethodCallError)
102
- end
103
-
104
- it 'returns self' do
105
- deferred = Defer.new{ nil }
106
- deferred.rescue{|ex| nil }.should eq deferred
107
- end
108
-
109
- it 'aliases #catch' do
110
- lambda {
111
- Defer.new{ nil }.catch{|ex| nil }
112
- }.should_not raise_error
113
- end
114
-
115
- it 'aliases #on_error' do
116
- lambda {
117
- Defer.new{ nil }.on_error{|ex| nil }
118
- }.should_not raise_error
119
- end
120
- end
121
-
122
- context '#go' do
123
-
124
- it 'starts the thread if not started' do
125
- deferred = Defer.new{ nil }
126
- Defer.thread_pool.should_receive(:post).once.with(any_args())
127
- deferred.go
128
- end
129
-
130
- it 'does nothing if called more than once' do
131
- deferred = Defer.new{ nil }
132
- deferred.go
133
- Defer.thread_pool.should_not_receive(:post)
134
- deferred.go
135
- end
136
-
137
- it 'does nothing if thread started at construction' do
138
- operation = proc{ nil }
139
- callback = proc{|result| nil }
140
- errorback = proc{|ex| nil }
141
- deferred = Defer.new(op: operation, callback: callback, errorback: errorback)
142
- Defer.thread_pool.should_not_receive(:post)
143
- deferred.go
144
- end
145
- end
146
-
147
- context 'fulfillment' do
148
-
149
- it 'runs the operation' do
150
- @expected = false
151
- Defer.new{ @expected = true }.go
152
- sleep(0.1)
153
- @expected.should be_true
154
- end
155
-
156
- it 'calls the callback when the operation is successful' do
157
- @expected = false
158
- Defer.new{ true }.then{|result| @expected = true }.go
159
- sleep(0.1)
160
- @expected.should be_true
161
- end
162
-
163
- it 'passes the result of the block to the callback' do
164
- @expected = false
165
- Defer.new{ 'w00t' }.then{|result| @expected = result }.go
166
- sleep(0.1)
167
- @expected.should eq 'w00t'
168
- end
169
-
170
- it 'does not call the errorback when the operation is successful' do
171
- @expected = true
172
- Defer.new{ nil }.rescue{|ex| @expected = false }.go
173
- sleep(0.1)
174
- @expected.should be_true
175
- end
176
-
177
- it 'calls the errorback if the operation throws an exception' do
178
- @expected = false
179
- Defer.new{ raise StandardError }.rescue{|ex| @expected = true }.go
180
- sleep(0.1)
181
- @expected.should be_true
182
- end
183
-
184
- it 'passes the exception object to the errorback' do
185
- @expected = nil
186
- Defer.new{ raise StandardError }.rescue{|ex| @expected = ex }.go
187
- sleep(0.1)
188
- @expected.should be_a(StandardError)
189
- end
190
-
191
- it 'does not call the callback when the operation fails' do
192
- @expected = true
193
- Defer.new{ raise StandardError }.then{|result| @expected = false }.go
194
- sleep(0.1)
195
- @expected.should be_true
196
- end
197
- end
198
- end
199
- end