concurrent-ruby 0.1.1.pre.2 → 0.1.1.pre.3
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.
- 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
|