concurrent-ruby 0.1.0 → 0.1.1.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -21
- data/README.md +279 -224
- data/lib/concurrent.rb +27 -20
- data/lib/concurrent/agent.rb +106 -130
- data/lib/concurrent/cached_thread_pool.rb +130 -122
- data/lib/concurrent/defer.rb +67 -69
- data/lib/concurrent/drb_async_demux.rb +72 -0
- data/lib/concurrent/event.rb +60 -60
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
- data/lib/concurrent/executor.rb +87 -0
- data/lib/concurrent/fixed_thread_pool.rb +89 -89
- data/lib/concurrent/functions.rb +120 -0
- data/lib/concurrent/future.rb +52 -42
- data/lib/concurrent/global_thread_pool.rb +3 -3
- data/lib/concurrent/goroutine.rb +29 -25
- data/lib/concurrent/obligation.rb +67 -121
- data/lib/concurrent/promise.rb +172 -194
- data/lib/concurrent/reactor.rb +162 -0
- data/lib/concurrent/smart_mutex.rb +66 -0
- data/lib/concurrent/tcp_sync_demux.rb +96 -0
- data/lib/concurrent/thread_pool.rb +65 -61
- data/lib/concurrent/utilities.rb +34 -0
- data/lib/concurrent/version.rb +3 -3
- data/lib/concurrent_ruby.rb +1 -1
- data/md/agent.md +123 -123
- data/md/defer.md +174 -174
- data/md/event.md +32 -32
- data/md/executor.md +176 -0
- data/md/future.md +83 -83
- data/md/goroutine.md +52 -52
- data/md/obligation.md +32 -32
- data/md/promise.md +225 -225
- data/md/thread_pool.md +197 -197
- data/spec/concurrent/agent_spec.rb +376 -405
- data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
- data/spec/concurrent/defer_spec.rb +209 -199
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
- data/spec/concurrent/event_spec.rb +134 -134
- data/spec/concurrent/executor_spec.rb +146 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
- data/spec/concurrent/functions_spec.rb +57 -0
- data/spec/concurrent/future_spec.rb +125 -115
- data/spec/concurrent/goroutine_spec.rb +67 -52
- data/spec/concurrent/obligation_shared.rb +121 -121
- data/spec/concurrent/promise_spec.rb +299 -310
- data/spec/concurrent/smart_mutex_spec.rb +234 -0
- data/spec/concurrent/thread_pool_shared.rb +209 -209
- data/spec/concurrent/utilities_spec.rb +74 -0
- data/spec/spec_helper.rb +21 -19
- metadata +38 -14
- checksums.yaml +0 -7
data/lib/concurrent/defer.rb
CHANGED
@@ -1,69 +1,67 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/global_thread_pool'
|
4
|
-
|
5
|
-
module Concurrent
|
6
|
-
|
7
|
-
IllegalMethodCallError = Class.new(StandardError)
|
8
|
-
|
9
|
-
class Defer
|
10
|
-
|
11
|
-
def initialize(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
return nil
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
module_function :defer
|
69
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
IllegalMethodCallError = Class.new(StandardError)
|
8
|
+
|
9
|
+
class Defer
|
10
|
+
|
11
|
+
def initialize(opts = {}, &block)
|
12
|
+
operation = opts[:op] || opts[:operation]
|
13
|
+
@callback = opts[:cback] || opts[:callback]
|
14
|
+
@errorback = opts[:eback] || opts[:error] || opts[:errorback]
|
15
|
+
thread_pool = opts[:pool] || opts[:thread_pool]
|
16
|
+
|
17
|
+
raise ArgumentError.new('no operation given') if operation.nil? && ! block_given?
|
18
|
+
raise ArgumentError.new('two operations given') if ! operation.nil? && block_given?
|
19
|
+
|
20
|
+
@operation = operation || block
|
21
|
+
|
22
|
+
if operation.nil?
|
23
|
+
@running = false
|
24
|
+
else
|
25
|
+
self.go(thread_pool)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def then(&block)
|
30
|
+
raise IllegalMethodCallError.new('a callback has already been provided') unless @callback.nil?
|
31
|
+
raise IllegalMethodCallError.new('the defer is already running') if @running
|
32
|
+
raise ArgumentError.new('no block given') unless block_given?
|
33
|
+
@callback = block
|
34
|
+
return self
|
35
|
+
end
|
36
|
+
|
37
|
+
def rescue(&block)
|
38
|
+
raise IllegalMethodCallError.new('a errorback has already been provided') unless @errorback.nil?
|
39
|
+
raise IllegalMethodCallError.new('the defer is already running') if @running
|
40
|
+
raise ArgumentError.new('no block given') unless block_given?
|
41
|
+
@errorback = block
|
42
|
+
return self
|
43
|
+
end
|
44
|
+
alias_method :catch, :rescue
|
45
|
+
alias_method :on_error, :rescue
|
46
|
+
|
47
|
+
def go(thread_pool = nil)
|
48
|
+
return nil if @running
|
49
|
+
atomic {
|
50
|
+
thread_pool ||= $GLOBAL_THREAD_POOL
|
51
|
+
@running = true
|
52
|
+
thread_pool.post { Thread.pass; fulfill }
|
53
|
+
}
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# @private
|
60
|
+
def fulfill # :nodoc:
|
61
|
+
result = @operation.call
|
62
|
+
@callback.call(result) unless @callback.nil?
|
63
|
+
rescue Exception => ex
|
64
|
+
@errorback.call(ex) unless @errorback.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'drb/acl'
|
3
|
+
require 'functional'
|
4
|
+
require 'concurrent/reactor'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
class DRbAsyncDemux
|
9
|
+
|
10
|
+
behavior(:async_event_demux)
|
11
|
+
|
12
|
+
DEFAULT_URI = 'druby://localhost:12345'
|
13
|
+
DEFAULT_ACL = %[allow all]
|
14
|
+
|
15
|
+
def initialize(opts = {})
|
16
|
+
@uri = opts[:uri] || DEFAULT_URI
|
17
|
+
@acl = ACL.new(opts[:acl] || DEFAULT_ACL)
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_reactor(reactor)
|
21
|
+
raise ArgumentError.new('invalid reactor') unless reactor.behaves_as?(:demux_reactor)
|
22
|
+
@reactor = reactor
|
23
|
+
end
|
24
|
+
|
25
|
+
def start
|
26
|
+
DRb.install_acl(@acl)
|
27
|
+
@service = DRb.start_service(@uri, Demultiplexer.new(@reactor))
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@service = DRb.stop_service
|
32
|
+
end
|
33
|
+
|
34
|
+
def stopped?
|
35
|
+
return @service.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
class Demultiplexer
|
41
|
+
|
42
|
+
def initialize(reactor)
|
43
|
+
@reactor = reactor
|
44
|
+
end
|
45
|
+
|
46
|
+
Concurrent::Reactor::RESERVED_EVENTS.each do |event|
|
47
|
+
define_method(event){|*args| false }
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_missing(method, *args, &block)
|
51
|
+
(class << self; self; end).class_eval do
|
52
|
+
define_method(method) do |*args|
|
53
|
+
result = @reactor.handle(method, *args)
|
54
|
+
case result.first
|
55
|
+
when :ok
|
56
|
+
return result.last
|
57
|
+
when :ex
|
58
|
+
raise result.last
|
59
|
+
when :noop
|
60
|
+
raise NoMethodError.new("undefined method '#{method}' for #{self}")
|
61
|
+
else
|
62
|
+
raise DRb::DRbUnknownError.new("unexpected error when calling method '#{method}'")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
self.send(method, *args)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
DRbAsyncDemultiplexer = DRbAsyncDemux
|
72
|
+
end
|
data/lib/concurrent/event.rb
CHANGED
@@ -1,60 +1,60 @@
|
|
1
|
-
require 'thread'
|
2
|
-
require 'timeout'
|
3
|
-
|
4
|
-
module Concurrent
|
5
|
-
|
6
|
-
class Event
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@set = false
|
10
|
-
@notifier = Queue.new
|
11
|
-
@mutex = Mutex.new
|
12
|
-
@waiting = 0
|
13
|
-
end
|
14
|
-
|
15
|
-
def set?
|
16
|
-
return @set == true
|
17
|
-
end
|
18
|
-
|
19
|
-
def set(pulse = false)
|
20
|
-
return true if set?
|
21
|
-
@mutex.synchronize {
|
22
|
-
@set = true
|
23
|
-
while @waiting > 0
|
24
|
-
@notifier << :set
|
25
|
-
@waiting -= 1
|
26
|
-
end
|
27
|
-
@set = ! pulse
|
28
|
-
}
|
29
|
-
return true
|
30
|
-
end
|
31
|
-
|
32
|
-
def reset
|
33
|
-
@mutex.synchronize {
|
34
|
-
@set = false
|
35
|
-
}
|
36
|
-
return true
|
37
|
-
end
|
38
|
-
|
39
|
-
def pulse
|
40
|
-
return set(true)
|
41
|
-
end
|
42
|
-
|
43
|
-
def wait(timeout = nil)
|
44
|
-
return true if set?
|
45
|
-
|
46
|
-
if timeout.nil?
|
47
|
-
@waiting += 1
|
48
|
-
@notifier.pop
|
49
|
-
else
|
50
|
-
Timeout::timeout(timeout) do
|
51
|
-
@waiting += 1
|
52
|
-
@notifier.pop
|
53
|
-
end
|
54
|
-
end
|
55
|
-
return true
|
56
|
-
rescue Timeout::Error
|
57
|
-
return false
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
class Event
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@set = false
|
10
|
+
@notifier = Queue.new
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@waiting = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def set?
|
16
|
+
return @set == true
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(pulse = false)
|
20
|
+
return true if set?
|
21
|
+
@mutex.synchronize {
|
22
|
+
@set = true
|
23
|
+
while @waiting > 0
|
24
|
+
@notifier << :set
|
25
|
+
@waiting -= 1
|
26
|
+
end
|
27
|
+
@set = ! pulse
|
28
|
+
}
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
|
32
|
+
def reset
|
33
|
+
@mutex.synchronize {
|
34
|
+
@set = false
|
35
|
+
}
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
def pulse
|
40
|
+
return set(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
def wait(timeout = nil)
|
44
|
+
return true if set?
|
45
|
+
|
46
|
+
if timeout.nil?
|
47
|
+
@waiting += 1
|
48
|
+
@notifier.pop
|
49
|
+
else
|
50
|
+
Timeout::timeout(timeout) do
|
51
|
+
@waiting += 1
|
52
|
+
@notifier.pop
|
53
|
+
end
|
54
|
+
end
|
55
|
+
return true
|
56
|
+
rescue Timeout::Error
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,23 +1,23 @@
|
|
1
|
-
require 'concurrent/global_thread_pool'
|
2
|
-
|
3
|
-
module Concurrent
|
4
|
-
|
5
|
-
class EventMachineDeferProxy
|
6
|
-
behavior(:global_thread_pool)
|
7
|
-
|
8
|
-
def post(*args, &block)
|
9
|
-
if args.empty?
|
10
|
-
EventMachine.defer(block)
|
11
|
-
else
|
12
|
-
new_block = proc{ block.call(*args) }
|
13
|
-
EventMachine.defer(new_block)
|
14
|
-
end
|
15
|
-
return true
|
16
|
-
end
|
17
|
-
|
18
|
-
def <<(block)
|
19
|
-
EventMachine.defer(block)
|
20
|
-
return self
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
1
|
+
require 'concurrent/global_thread_pool'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
class EventMachineDeferProxy
|
6
|
+
behavior(:global_thread_pool)
|
7
|
+
|
8
|
+
def post(*args, &block)
|
9
|
+
if args.empty?
|
10
|
+
EventMachine.defer(block)
|
11
|
+
else
|
12
|
+
new_block = proc{ block.call(*args) }
|
13
|
+
EventMachine.defer(new_block)
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(block)
|
19
|
+
EventMachine.defer(block)
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
module Executor
|
6
|
+
extend self
|
7
|
+
|
8
|
+
class ExecutionContext
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :execution_interval
|
11
|
+
attr_reader :timeout_interval
|
12
|
+
|
13
|
+
def initialize(name, execution_interval, timeout_interval, thread)
|
14
|
+
@name = name
|
15
|
+
@execution_interval = execution_interval
|
16
|
+
@timeout_interval = timeout_interval
|
17
|
+
@thread = thread
|
18
|
+
@thread[:stop] = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def status
|
22
|
+
return @thread.status unless @thread.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def join(limit = nil)
|
26
|
+
if @thread.nil?
|
27
|
+
return nil
|
28
|
+
elsif limit.nil?
|
29
|
+
return @thread.join
|
30
|
+
else
|
31
|
+
return @thread.join(limit)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
@thread[:stop] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def kill
|
40
|
+
@thread.kill
|
41
|
+
@thread = nil
|
42
|
+
end
|
43
|
+
alias_method :terminate, :kill
|
44
|
+
alias_method :stop, :kill
|
45
|
+
end
|
46
|
+
|
47
|
+
EXECUTION_INTERVAL = 60
|
48
|
+
TIMEOUT_INTERVAL = 30
|
49
|
+
|
50
|
+
STDOUT_LOGGER = proc do |name, level, msg|
|
51
|
+
print "%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg]
|
52
|
+
end
|
53
|
+
|
54
|
+
def run(name, opts = {})
|
55
|
+
raise ArgumentError.new('no block given') unless block_given?
|
56
|
+
|
57
|
+
execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
|
58
|
+
timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
|
59
|
+
logger = opts[:logger] || STDOUT_LOGGER
|
60
|
+
block_args = opts[:args] || opts [:arguments] || []
|
61
|
+
|
62
|
+
executor = Thread.new(*block_args) do |*args|
|
63
|
+
loop do
|
64
|
+
sleep(execution_interval)
|
65
|
+
break if Thread.current[:stop]
|
66
|
+
begin
|
67
|
+
worker = Thread.new{ yield(*args) }
|
68
|
+
worker.abort_on_exception = false
|
69
|
+
if worker.join(timeout_interval).nil?
|
70
|
+
logger.call(name, :warn, "execution timed out after #{timeout_interval} seconds")
|
71
|
+
else
|
72
|
+
logger.call(name, :info, 'execution completed successfully')
|
73
|
+
end
|
74
|
+
rescue Exception => ex
|
75
|
+
logger.call(name, :error, "execution failed with error '#{ex}'")
|
76
|
+
ensure
|
77
|
+
Thread.kill(worker)
|
78
|
+
worker = nil
|
79
|
+
end
|
80
|
+
break if Thread.current[:stop]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return ExecutionContext.new(name, execution_interval, timeout_interval, executor)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|