concurrent-ruby 0.2.2 → 0.3.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 (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