concurrent-ruby 0.1.1.pre.2 → 0.1.1.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/concurrent.rb +4 -3
- data/lib/concurrent/agent.rb +3 -1
- data/lib/concurrent/defer.rb +4 -4
- data/lib/concurrent/executor.rb +4 -0
- data/lib/concurrent/functions.rb +30 -30
- data/lib/concurrent/future.rb +3 -8
- data/lib/concurrent/global_thread_pool.rb +13 -0
- data/lib/concurrent/null_thread_pool.rb +22 -0
- data/lib/concurrent/promise.rb +4 -10
- data/lib/concurrent/reactor/drb_async_demux.rb +74 -0
- data/lib/concurrent/reactor/tcp_sync_demux.rb +98 -0
- data/lib/concurrent/thread_pool.rb +7 -3
- data/lib/concurrent/version.rb +1 -1
- data/md/defer.md +4 -4
- data/md/promise.md +2 -0
- data/md/thread_pool.md +27 -0
- data/spec/concurrent/agent_spec.rb +5 -1
- data/spec/concurrent/cached_thread_pool_spec.rb +13 -0
- data/spec/concurrent/defer_spec.rb +9 -23
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +13 -10
- data/spec/concurrent/executor_spec.rb +38 -0
- data/spec/concurrent/functions_spec.rb +160 -0
- data/spec/concurrent/future_spec.rb +2 -19
- data/spec/concurrent/global_thread_pool_spec.rb +38 -0
- data/spec/concurrent/null_thread_pool_spec.rb +54 -0
- data/spec/concurrent/promise_spec.rb +6 -0
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +12 -0
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +12 -0
- data/spec/concurrent/reactor_spec.rb +10 -0
- data/spec/concurrent/thread_pool_shared.rb +3 -2
- data/spec/spec_helper.rb +9 -0
- metadata +15 -4
- data/lib/concurrent/drb_async_demux.rb +0 -72
- data/lib/concurrent/tcp_sync_demux.rb +0 -96
data/lib/concurrent.rb
CHANGED
@@ -11,16 +11,17 @@ require 'concurrent/future'
|
|
11
11
|
require 'concurrent/goroutine'
|
12
12
|
require 'concurrent/promise'
|
13
13
|
require 'concurrent/obligation'
|
14
|
-
require 'concurrent/reactor'
|
15
14
|
require 'concurrent/smart_mutex'
|
16
15
|
require 'concurrent/utilities'
|
17
16
|
|
18
|
-
require 'concurrent/
|
19
|
-
require 'concurrent/
|
17
|
+
require 'concurrent/reactor'
|
18
|
+
require 'concurrent/reactor/drb_async_demux'
|
19
|
+
require 'concurrent/reactor/tcp_sync_demux'
|
20
20
|
|
21
21
|
require 'concurrent/thread_pool'
|
22
22
|
require 'concurrent/cached_thread_pool'
|
23
23
|
require 'concurrent/fixed_thread_pool'
|
24
|
+
require 'concurrent/null_thread_pool'
|
24
25
|
|
25
26
|
require 'concurrent/global_thread_pool'
|
26
27
|
|
data/lib/concurrent/agent.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'observer'
|
2
2
|
require 'thread'
|
3
3
|
|
4
|
+
require 'concurrent/global_thread_pool'
|
4
5
|
require 'concurrent/utilities'
|
5
6
|
|
6
7
|
module Concurrent
|
@@ -13,6 +14,7 @@ module Concurrent
|
|
13
14
|
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
14
15
|
class Agent
|
15
16
|
include Observable
|
17
|
+
include UsesGlobalThreadPool
|
16
18
|
|
17
19
|
TIMEOUT = 5
|
18
20
|
|
@@ -26,7 +28,7 @@ module Concurrent
|
|
26
28
|
@validator = nil
|
27
29
|
@queue = Queue.new
|
28
30
|
|
29
|
-
|
31
|
+
Agent.thread_pool.post{ work }
|
30
32
|
end
|
31
33
|
|
32
34
|
def value(timeout = 0) return @value; end
|
data/lib/concurrent/defer.rb
CHANGED
@@ -7,6 +7,7 @@ module Concurrent
|
|
7
7
|
IllegalMethodCallError = Class.new(StandardError)
|
8
8
|
|
9
9
|
class Defer
|
10
|
+
include UsesGlobalThreadPool
|
10
11
|
|
11
12
|
def initialize(opts = {}, &block)
|
12
13
|
operation = opts[:op] || opts[:operation]
|
@@ -22,7 +23,7 @@ module Concurrent
|
|
22
23
|
if operation.nil?
|
23
24
|
@running = false
|
24
25
|
else
|
25
|
-
self.go
|
26
|
+
self.go
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
@@ -44,12 +45,11 @@ module Concurrent
|
|
44
45
|
alias_method :catch, :rescue
|
45
46
|
alias_method :on_error, :rescue
|
46
47
|
|
47
|
-
def go
|
48
|
+
def go
|
48
49
|
return nil if @running
|
49
50
|
atomic {
|
50
|
-
thread_pool ||= $GLOBAL_THREAD_POOL
|
51
51
|
@running = true
|
52
|
-
thread_pool.post { Thread.pass; fulfill }
|
52
|
+
Defer.thread_pool.post { Thread.pass; fulfill }
|
53
53
|
}
|
54
54
|
return nil
|
55
55
|
end
|
data/lib/concurrent/executor.rb
CHANGED
@@ -10,6 +10,8 @@ module Concurrent
|
|
10
10
|
attr_reader :execution_interval
|
11
11
|
attr_reader :timeout_interval
|
12
12
|
|
13
|
+
protected
|
14
|
+
|
13
15
|
def initialize(name, execution_interval, timeout_interval, thread)
|
14
16
|
@name = name
|
15
17
|
@execution_interval = execution_interval
|
@@ -18,6 +20,8 @@ module Concurrent
|
|
18
20
|
@thread[:stop] = false
|
19
21
|
end
|
20
22
|
|
23
|
+
public
|
24
|
+
|
21
25
|
def status
|
22
26
|
return @thread.status unless @thread.nil?
|
23
27
|
end
|
data/lib/concurrent/functions.rb
CHANGED
@@ -12,11 +12,11 @@ module Kernel
|
|
12
12
|
end
|
13
13
|
module_function :agent
|
14
14
|
|
15
|
-
def post(
|
16
|
-
if
|
17
|
-
return
|
15
|
+
def post(object, &block)
|
16
|
+
if object.respond_to?(:post)
|
17
|
+
return object.post(&block)
|
18
18
|
else
|
19
|
-
|
19
|
+
raise ArgumentError.new('object does not support #post')
|
20
20
|
end
|
21
21
|
end
|
22
22
|
module_function :post
|
@@ -44,53 +44,53 @@ module Kernel
|
|
44
44
|
|
45
45
|
## obligation
|
46
46
|
|
47
|
-
def deref(
|
48
|
-
if
|
49
|
-
return
|
50
|
-
elsif
|
51
|
-
return
|
47
|
+
def deref(object, timeout = nil)
|
48
|
+
if object.respond_to?(:deref)
|
49
|
+
return object.deref(timeout)
|
50
|
+
elsif object.respond_to?(:value)
|
51
|
+
return object.value(timeout)
|
52
52
|
else
|
53
|
-
|
53
|
+
raise ArgumentError.new('object does not support #deref')
|
54
54
|
end
|
55
55
|
end
|
56
56
|
module_function :deref
|
57
57
|
|
58
|
-
def pending?(
|
59
|
-
if
|
60
|
-
return
|
58
|
+
def pending?(object)
|
59
|
+
if object.respond_to?(:pending?)
|
60
|
+
return object.pending?
|
61
61
|
else
|
62
|
-
|
62
|
+
raise ArgumentError.new('object does not support #pending?')
|
63
63
|
end
|
64
64
|
end
|
65
65
|
module_function :pending?
|
66
66
|
|
67
|
-
def fulfilled?(
|
68
|
-
if
|
69
|
-
return
|
70
|
-
elsif
|
71
|
-
return
|
67
|
+
def fulfilled?(object)
|
68
|
+
if object.respond_to?(:fulfilled?)
|
69
|
+
return object.fulfilled?
|
70
|
+
elsif object.respond_to?(:realized?)
|
71
|
+
return object.realized?
|
72
72
|
else
|
73
|
-
|
73
|
+
raise ArgumentError.new('object does not support #fulfilled?')
|
74
74
|
end
|
75
75
|
end
|
76
76
|
module_function :fulfilled?
|
77
77
|
|
78
|
-
def realized?(
|
79
|
-
if
|
80
|
-
return
|
81
|
-
elsif
|
82
|
-
return
|
78
|
+
def realized?(object)
|
79
|
+
if object.respond_to?(:realized?)
|
80
|
+
return object.realized?
|
81
|
+
elsif object.respond_to?(:fulfilled?)
|
82
|
+
return object.fulfilled?
|
83
83
|
else
|
84
|
-
|
84
|
+
raise ArgumentError.new('object does not support #realized?')
|
85
85
|
end
|
86
86
|
end
|
87
87
|
module_function :realized?
|
88
88
|
|
89
|
-
def rejected?(
|
90
|
-
if
|
91
|
-
return
|
89
|
+
def rejected?(object)
|
90
|
+
if object.respond_to?(:rejected?)
|
91
|
+
return object.rejected?
|
92
92
|
else
|
93
|
-
|
93
|
+
raise ArgumentError.new('object does not support #rejected?')
|
94
94
|
end
|
95
95
|
end
|
96
96
|
module_function :rejected?
|
data/lib/concurrent/future.rb
CHANGED
@@ -8,22 +8,17 @@ module Concurrent
|
|
8
8
|
|
9
9
|
class Future
|
10
10
|
include Obligation
|
11
|
+
include UsesGlobalThreadPool
|
12
|
+
|
11
13
|
behavior(:future)
|
12
14
|
|
13
15
|
def initialize(*args, &block)
|
14
|
-
if args.first.behaves_as?(:global_thread_pool)
|
15
|
-
thread_pool = args.first
|
16
|
-
args = args.slice(1, args.length)
|
17
|
-
else
|
18
|
-
thread_pool = $GLOBAL_THREAD_POOL
|
19
|
-
end
|
20
|
-
|
21
16
|
unless block_given?
|
22
17
|
@state = :fulfilled
|
23
18
|
else
|
24
19
|
@value = nil
|
25
20
|
@state = :pending
|
26
|
-
thread_pool.post do
|
21
|
+
Future.thread_pool.post(*args) do
|
27
22
|
Thread.pass
|
28
23
|
work(*args, &block)
|
29
24
|
end
|
@@ -1,3 +1,16 @@
|
|
1
1
|
require 'concurrent/cached_thread_pool'
|
2
2
|
|
3
3
|
$GLOBAL_THREAD_POOL ||= Concurrent::CachedThreadPool.new
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
module UsesGlobalThreadPool
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
class << base
|
11
|
+
attr_accessor :thread_pool
|
12
|
+
end
|
13
|
+
base.thread_pool = $GLOBAL_THREAD_POOL
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'concurrent/global_thread_pool'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
class NullThreadPool
|
6
|
+
behavior(:global_thread_pool)
|
7
|
+
|
8
|
+
def self.post(*args, &block)
|
9
|
+
Thread.new(*args, &block)
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(*args, &block)
|
14
|
+
return NullThreadPool.post(*args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(block)
|
18
|
+
NullThreadPool.post(&block)
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/concurrent/promise.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
+
require 'concurrent/global_thread_pool'
|
3
4
|
require 'concurrent/obligation'
|
4
5
|
require 'concurrent/utilities'
|
5
6
|
|
@@ -7,6 +8,8 @@ module Concurrent
|
|
7
8
|
|
8
9
|
class Promise
|
9
10
|
include Obligation
|
11
|
+
include UsesGlobalThreadPool
|
12
|
+
|
10
13
|
behavior(:future)
|
11
14
|
behavior(:promise)
|
12
15
|
|
@@ -90,15 +93,6 @@ module Concurrent
|
|
90
93
|
# @private
|
91
94
|
Rescuer = Struct.new(:clazz, :block)
|
92
95
|
|
93
|
-
# @private
|
94
|
-
def root # :nodoc:
|
95
|
-
return atomic {
|
96
|
-
current = self
|
97
|
-
current = current.parent until current.root?
|
98
|
-
current
|
99
|
-
}
|
100
|
-
end
|
101
|
-
|
102
96
|
# @private
|
103
97
|
def root? # :nodoc:
|
104
98
|
@parent.nil?
|
@@ -148,7 +142,7 @@ module Concurrent
|
|
148
142
|
|
149
143
|
# @private
|
150
144
|
def realize(*args) # :nodoc:
|
151
|
-
|
145
|
+
Promise.thread_pool.post(@chain, @mutex, args) do |chain, mutex, args|
|
152
146
|
result = args.length == 1 ? args.first : args
|
153
147
|
index = 0
|
154
148
|
loop do
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'drb/acl'
|
3
|
+
require 'functional'
|
4
|
+
require 'concurrent/reactor'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
class Reactor
|
8
|
+
|
9
|
+
class DRbAsyncDemux
|
10
|
+
|
11
|
+
behavior(:async_event_demux)
|
12
|
+
|
13
|
+
DEFAULT_URI = 'druby://localhost:12345'
|
14
|
+
DEFAULT_ACL = %[allow all]
|
15
|
+
|
16
|
+
def initialize(opts = {})
|
17
|
+
@uri = opts[:uri] || DEFAULT_URI
|
18
|
+
@acl = ACL.new(opts[:acl] || DEFAULT_ACL)
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_reactor(reactor)
|
22
|
+
raise ArgumentError.new('invalid reactor') unless reactor.behaves_as?(:demux_reactor)
|
23
|
+
@reactor = reactor
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
DRb.install_acl(@acl)
|
28
|
+
@service = DRb.start_service(@uri, Demultiplexer.new(@reactor))
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
@service = DRb.stop_service
|
33
|
+
end
|
34
|
+
|
35
|
+
def stopped?
|
36
|
+
return @service.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
class Demultiplexer
|
42
|
+
|
43
|
+
def initialize(reactor)
|
44
|
+
@reactor = reactor
|
45
|
+
end
|
46
|
+
|
47
|
+
Concurrent::Reactor::RESERVED_EVENTS.each do |event|
|
48
|
+
define_method(event){|*args| false }
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(method, *args, &block)
|
52
|
+
(class << self; self; end).class_eval do
|
53
|
+
define_method(method) do |*args|
|
54
|
+
result = @reactor.handle(method, *args)
|
55
|
+
case result.first
|
56
|
+
when :ok
|
57
|
+
return result.last
|
58
|
+
when :ex
|
59
|
+
raise result.last
|
60
|
+
when :noop
|
61
|
+
raise NoMethodError.new("undefined method '#{method}' for #{self}")
|
62
|
+
else
|
63
|
+
raise DRb::DRbUnknownError.new("unexpected error when calling method '#{method}'")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
self.send(method, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
DRbAsyncDemultiplexer = DRbAsyncDemux
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'drb/acl'
|
3
|
+
require 'functional'
|
4
|
+
require 'concurrent/reactor'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
class Reactor
|
8
|
+
|
9
|
+
class TcpSyncDemux
|
10
|
+
|
11
|
+
behavior(:sync_event_demux)
|
12
|
+
|
13
|
+
DEFAULT_HOST = '127.0.0.1'
|
14
|
+
DEFAULT_PORT = 12345
|
15
|
+
DEFAULT_ACL = %[allow all]
|
16
|
+
|
17
|
+
def initialize(opts = {})
|
18
|
+
@host = opts[:host] || DEFAULT_HOST
|
19
|
+
@port = opts[:port] || DEFAULT_PORT
|
20
|
+
@acl = ACL.new(opts[:acl] || DEFAULT_ACL)
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@server = TCPServer.new(@host, @port)
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
atomic {
|
29
|
+
@socket.close unless @socket.nil?
|
30
|
+
@server.close unless @server.nil?
|
31
|
+
@server = @socket = nil
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def stopped?
|
36
|
+
return @server.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def accept
|
40
|
+
@socket = @server.accept if @socket.nil?
|
41
|
+
return nil unless @acl.allow_socket?(@socket)
|
42
|
+
event, args = get_message(@socket)
|
43
|
+
return nil if event.nil?
|
44
|
+
return Reactor::EventContext.new(event, args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond(result, message)
|
48
|
+
return nil if @socket.nil?
|
49
|
+
@socket.puts(format_message(result, message))
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@socket.close
|
54
|
+
@socket = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.format_message(event, *args)
|
58
|
+
args = args.reduce('') do |memo, arg|
|
59
|
+
memo << "#{arg}\r\n"
|
60
|
+
end
|
61
|
+
return ":#{event}\r\n#{args}\r\n"
|
62
|
+
end
|
63
|
+
def format_message(*args) self.class.format_message(*args); end
|
64
|
+
|
65
|
+
def self.parse_message(message)
|
66
|
+
return atomic {
|
67
|
+
event = message.first.match /^:?(\w+)/
|
68
|
+
event = event[1].to_s.downcase.to_sym unless event.nil?
|
69
|
+
|
70
|
+
args = message.slice(1, message.length) || []
|
71
|
+
|
72
|
+
[event, args]
|
73
|
+
}
|
74
|
+
end
|
75
|
+
def parse_message(*args) self.class.parse_message(*args); end
|
76
|
+
|
77
|
+
def self.get_message(socket)
|
78
|
+
message = []
|
79
|
+
while line = socket.gets
|
80
|
+
if line.nil? || (line = line.strip).empty?
|
81
|
+
break
|
82
|
+
else
|
83
|
+
message << line
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
if message.empty?
|
88
|
+
return nil
|
89
|
+
else
|
90
|
+
return parse_message(message)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
def get_message(*args) self.class.get_message(*args); end
|
94
|
+
end
|
95
|
+
|
96
|
+
TcpSyncDemultiplexer = TcpSyncDemux
|
97
|
+
end
|
98
|
+
end
|