celluloid 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -3
- data/lib/celluloid.rb +10 -12
- data/lib/celluloid/actor.rb +20 -39
- data/lib/celluloid/actor_proxy.rb +8 -7
- data/lib/celluloid/calls.rb +13 -7
- data/lib/celluloid/core_ext.rb +9 -0
- data/lib/celluloid/fsm.rb +37 -39
- data/lib/celluloid/future.rb +79 -31
- data/lib/celluloid/{application.rb → group.rb} +5 -2
- data/lib/celluloid/mailbox.rb +35 -33
- data/lib/celluloid/receivers.rb +2 -2
- data/lib/celluloid/responses.rb +3 -3
- data/lib/celluloid/signals.rb +5 -5
- data/lib/celluloid/task.rb +17 -9
- data/lib/celluloid/thread_pool.rb +57 -0
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +13 -14
- metadata +20 -9
- data/lib/celluloid/actor_pool.rb +0 -54
data/README.md
CHANGED
@@ -12,7 +12,7 @@ objects just as easily as you build sequential programs out of regular objects.
|
|
12
12
|
Recommended for any developer, including novices, Celluloid should help ease
|
13
13
|
your worries about building multithreaded Ruby programs:
|
14
14
|
|
15
|
-
*
|
15
|
+
* __Automatic synchronization:__ Celluloid synchronizes access to instance
|
16
16
|
variables by using a special proxy object system and messaging model.
|
17
17
|
* __[Futures](https://github.com/tarcieri/celluloid/wiki/futures):__
|
18
18
|
Ever wanted to call a method "in the background" and retrieve the
|
@@ -40,12 +40,15 @@ _asynchronously_, so the receiver to do things in the background for you
|
|
40
40
|
without the caller having to sit around waiting for the result.
|
41
41
|
|
42
42
|
You can also build distributed systems with Celluloid using its
|
43
|
-
[sister project DCell](https://github.com/tarcieri/dcell).
|
43
|
+
[sister project DCell](https://github.com/tarcieri/dcell). Evented IO similar
|
44
|
+
to EventMachine (albeit with a synchronous API) is available through the
|
45
|
+
[Celluloid::IO](https://github.com/tarcieri/celluloid-io) library.
|
44
46
|
|
45
47
|
[Please see the Celluloid Wiki](https://github.com/tarcieri/celluloid/wiki)
|
46
48
|
for more detailed documentation and usage notes.
|
47
49
|
|
48
50
|
Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
51
|
+
or visit us on IRC at #celluloid on freenode
|
49
52
|
|
50
53
|
Supported Platforms
|
51
54
|
-------------------
|
@@ -144,5 +147,5 @@ Contributing to Celluloid
|
|
144
147
|
License
|
145
148
|
-------
|
146
149
|
|
147
|
-
Copyright (c)
|
150
|
+
Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
|
148
151
|
LICENSE.txt for further details.
|
data/lib/celluloid.rb
CHANGED
@@ -43,13 +43,6 @@ module Celluloid
|
|
43
43
|
Kernel.sleep interval
|
44
44
|
end
|
45
45
|
end
|
46
|
-
|
47
|
-
# Obtain a hash of active tasks to their current activities
|
48
|
-
def tasks
|
49
|
-
actor = Thread.current[:actor]
|
50
|
-
raise NotActorError, "not in actor scope" unless actor
|
51
|
-
actor.tasks
|
52
|
-
end
|
53
46
|
end
|
54
47
|
|
55
48
|
# Class methods added to classes which include Celluloid
|
@@ -150,7 +143,7 @@ module Celluloid
|
|
150
143
|
|
151
144
|
# Obtain the running tasks for this actor
|
152
145
|
def tasks
|
153
|
-
|
146
|
+
Thread.current[:actor].tasks.to_a
|
154
147
|
end
|
155
148
|
|
156
149
|
# Obtain the Ruby object the actor is wrapping. This should ONLY be used
|
@@ -208,12 +201,17 @@ module Celluloid
|
|
208
201
|
# Perform a blocking or computationally intensive action inside an
|
209
202
|
# asynchronous thread pool, allowing the caller to continue processing other
|
210
203
|
# messages in its mailbox in the meantime
|
211
|
-
def
|
204
|
+
def defer(&block)
|
212
205
|
# This implementation relies on the present implementation of
|
213
206
|
# Celluloid::Future, which uses an Actor to run the block
|
214
207
|
Future.new(&block).value
|
215
208
|
end
|
216
209
|
|
210
|
+
# Deprecated, do not use
|
211
|
+
def async
|
212
|
+
raise "Celluloid#async is defunct. Please use #defer instead"
|
213
|
+
end
|
214
|
+
|
217
215
|
# Process async calls via method_missing
|
218
216
|
def method_missing(meth, *args, &block)
|
219
217
|
# bang methods are async calls
|
@@ -253,9 +251,9 @@ require 'celluloid/responses'
|
|
253
251
|
require 'celluloid/signals'
|
254
252
|
require 'celluloid/task'
|
255
253
|
require 'celluloid/timers'
|
254
|
+
require 'celluloid/thread_pool'
|
256
255
|
|
257
256
|
require 'celluloid/actor'
|
258
|
-
require 'celluloid/actor_pool'
|
259
|
-
require 'celluloid/supervisor'
|
260
257
|
require 'celluloid/future'
|
261
|
-
require 'celluloid/
|
258
|
+
require 'celluloid/group'
|
259
|
+
require 'celluloid/supervisor'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -20,10 +20,7 @@ module Celluloid
|
|
20
20
|
# messages.
|
21
21
|
class Actor
|
22
22
|
extend Registry
|
23
|
-
|
24
|
-
attr_reader :proxy
|
25
|
-
attr_reader :links
|
26
|
-
attr_reader :mailbox
|
23
|
+
attr_reader :proxy, :tasks, :links, :mailbox
|
27
24
|
|
28
25
|
# Invoke a method on the given actor via its mailbox
|
29
26
|
def self.call(mailbox, meth, *args, &block)
|
@@ -36,15 +33,16 @@ module Celluloid
|
|
36
33
|
end
|
37
34
|
|
38
35
|
if Celluloid.actor?
|
39
|
-
|
36
|
+
# The current task will be automatically resumed when we get a response
|
37
|
+
Task.suspend :callwait
|
40
38
|
else
|
41
39
|
# Otherwise we're inside a normal thread, so block
|
42
40
|
response = Thread.mailbox.receive do |msg|
|
43
|
-
msg.respond_to?(:
|
41
|
+
msg.respond_to?(:call) and msg.call == call
|
44
42
|
end
|
45
|
-
end
|
46
43
|
|
47
|
-
|
44
|
+
response.value
|
45
|
+
end
|
48
46
|
end
|
49
47
|
|
50
48
|
# Invoke a method asynchronously on an actor via its mailbox
|
@@ -59,6 +57,13 @@ module Celluloid
|
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
60
|
+
# Call a method asynchronously and retrieve its value later
|
61
|
+
def self.future(mailbox, meth, *args, &block)
|
62
|
+
future = Future.new
|
63
|
+
future.execute(mailbox, meth, args, block)
|
64
|
+
future
|
65
|
+
end
|
66
|
+
|
62
67
|
# Wrap the given subject with an Actor
|
63
68
|
def initialize(subject)
|
64
69
|
@subject = subject
|
@@ -69,14 +74,15 @@ module Celluloid
|
|
69
74
|
@mailbox = Mailbox.new
|
70
75
|
end
|
71
76
|
|
77
|
+
@proxy = ActorProxy.new(@mailbox, subject.class.to_s)
|
78
|
+
@tasks = Set.new
|
72
79
|
@links = Links.new
|
73
80
|
@signals = Signals.new
|
74
81
|
@receivers = Receivers.new
|
75
82
|
@timers = Timers.new
|
76
|
-
@proxy = ActorProxy.new(@mailbox, subject.class.to_s)
|
77
83
|
@running = true
|
78
84
|
|
79
|
-
@thread =
|
85
|
+
@thread = ThreadPool.get do
|
80
86
|
Thread.current[:actor] = self
|
81
87
|
Thread.current[:mailbox] = @mailbox
|
82
88
|
|
@@ -137,7 +143,7 @@ module Celluloid
|
|
137
143
|
@running = false
|
138
144
|
handle_crash(ex)
|
139
145
|
ensure
|
140
|
-
|
146
|
+
ThreadPool.put @thread
|
141
147
|
end
|
142
148
|
|
143
149
|
# How long to wait until the next timer fires
|
@@ -154,27 +160,6 @@ module Celluloid
|
|
154
160
|
end
|
155
161
|
end
|
156
162
|
|
157
|
-
# Obtain a hash of tasks that are currently waiting
|
158
|
-
def tasks
|
159
|
-
# A hash of tasks to what they're waiting on is more meaningful to the
|
160
|
-
# end-user, and lets us make a copy of the tasks table, rather than
|
161
|
-
# handing them the one we're using internally across threads, a definite
|
162
|
-
# thread safety shared state no-no
|
163
|
-
tasks = {}
|
164
|
-
current_task = Task.current rescue nil
|
165
|
-
tasks[current_task] = :running if current_task
|
166
|
-
|
167
|
-
@signals.waiting.each do |waitable, waiters|
|
168
|
-
if waiters.is_a? Enumerable
|
169
|
-
waiters.each { |waiter| tasks[waiter] = waitable }
|
170
|
-
else
|
171
|
-
tasks[waiters] = waitable
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
tasks
|
176
|
-
end
|
177
|
-
|
178
163
|
# Schedule a block to run at the given time
|
179
164
|
def after(interval)
|
180
165
|
@timers.add(interval) do
|
@@ -186,7 +171,7 @@ module Celluloid
|
|
186
171
|
def sleep(interval)
|
187
172
|
task = Task.current
|
188
173
|
@timers.add(interval) { task.resume }
|
189
|
-
Task.suspend
|
174
|
+
Task.suspend :sleeping
|
190
175
|
end
|
191
176
|
|
192
177
|
# Handle an incoming message
|
@@ -195,11 +180,7 @@ module Celluloid
|
|
195
180
|
when Call
|
196
181
|
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
197
182
|
when Response
|
198
|
-
|
199
|
-
|
200
|
-
unless handled_successfully
|
201
|
-
Logger.debug("anomalous message! spurious response to call #{message.call_id}")
|
202
|
-
end
|
183
|
+
message.call.task.resume message.value
|
203
184
|
else
|
204
185
|
@receivers.handle_message(message)
|
205
186
|
end
|
@@ -230,7 +211,7 @@ module Celluloid
|
|
230
211
|
def cleanup(exit_event)
|
231
212
|
@mailbox.shutdown
|
232
213
|
@links.send_event exit_event
|
233
|
-
tasks.each { |task
|
214
|
+
tasks.each { |task| task.terminate }
|
234
215
|
|
235
216
|
begin
|
236
217
|
@subject.finalize if @subject.respond_to? :finalize
|
@@ -41,7 +41,7 @@ module Celluloid
|
|
41
41
|
|
42
42
|
# Create a Celluloid::Future which calls a given method
|
43
43
|
def future(method_name, *args, &block)
|
44
|
-
|
44
|
+
Actor.future @mailbox, method_name, *args, &block
|
45
45
|
end
|
46
46
|
|
47
47
|
# Terminate the associated actor
|
@@ -62,14 +62,15 @@ module Celluloid
|
|
62
62
|
|
63
63
|
# method_missing black magic to call bang predicate methods asynchronously
|
64
64
|
def method_missing(meth, *args, &block)
|
65
|
+
meth = meth.to_s
|
66
|
+
|
65
67
|
# bang methods are async calls
|
66
|
-
if meth.
|
67
|
-
|
68
|
-
Actor.async @mailbox,
|
69
|
-
|
68
|
+
if meth.match(/!$/)
|
69
|
+
meth.sub!(/!$/, '')
|
70
|
+
Actor.async @mailbox, meth, *args, &block
|
71
|
+
else
|
72
|
+
Actor.call @mailbox, meth, *args, &block
|
70
73
|
end
|
71
|
-
|
72
|
-
Actor.call @mailbox, meth, *args, &block
|
73
74
|
end
|
74
75
|
end
|
75
76
|
end
|
data/lib/celluloid/calls.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Calls represent requests to an actor
|
3
3
|
class Call
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :caller, :method, :arguments, :block
|
5
5
|
|
6
|
-
def initialize(caller, method, arguments, block)
|
7
|
-
@id = object_id # memoize object ID for serialization
|
6
|
+
def initialize(caller, method, arguments = [], block = nil)
|
8
7
|
@caller, @method, @arguments, @block = caller, method, arguments, block
|
9
8
|
end
|
10
9
|
|
@@ -29,11 +28,18 @@ module Celluloid
|
|
29
28
|
|
30
29
|
# Synchronous calls wait for a response
|
31
30
|
class SyncCall < Call
|
31
|
+
attr_reader :task
|
32
|
+
|
33
|
+
def initialize(caller, method, arguments = [], block = nil, task = Fiber.current.task)
|
34
|
+
super(caller, method, arguments, block)
|
35
|
+
@task = task
|
36
|
+
end
|
37
|
+
|
32
38
|
def dispatch(obj)
|
33
39
|
begin
|
34
40
|
check_signature(obj)
|
35
41
|
rescue Exception => ex
|
36
|
-
respond ErrorResponse.new(
|
42
|
+
respond ErrorResponse.new(self, AbortError.new(ex))
|
37
43
|
return
|
38
44
|
end
|
39
45
|
|
@@ -42,7 +48,7 @@ module Celluloid
|
|
42
48
|
rescue Exception => exception
|
43
49
|
# Exceptions that occur during synchronous calls are reraised in the
|
44
50
|
# context of the caller
|
45
|
-
respond ErrorResponse.new(
|
51
|
+
respond ErrorResponse.new(self, exception)
|
46
52
|
|
47
53
|
if exception.is_a? AbortError
|
48
54
|
# Aborting indicates a protocol error on the part of the caller
|
@@ -54,12 +60,12 @@ module Celluloid
|
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
57
|
-
respond SuccessResponse.new(
|
63
|
+
respond SuccessResponse.new(self, result)
|
58
64
|
end
|
59
65
|
|
60
66
|
def cleanup
|
61
67
|
exception = DeadActorError.new("attempted to call a dead actor")
|
62
|
-
respond ErrorResponse.new(
|
68
|
+
respond ErrorResponse.new(self, exception)
|
63
69
|
end
|
64
70
|
|
65
71
|
#######
|
data/lib/celluloid/core_ext.rb
CHANGED
@@ -6,6 +6,15 @@ class Thread
|
|
6
6
|
def self.mailbox
|
7
7
|
current[:mailbox] ||= Celluloid::Mailbox.new
|
8
8
|
end
|
9
|
+
|
10
|
+
# Receive a message either as an actor or through the local mailbox
|
11
|
+
def self.receive(&block)
|
12
|
+
if Celluloid.actor?
|
13
|
+
Celluloid.receive(&block)
|
14
|
+
else
|
15
|
+
mailbox.receive(&block)
|
16
|
+
end
|
17
|
+
end
|
9
18
|
end
|
10
19
|
|
11
20
|
class Fiber
|
data/lib/celluloid/fsm.rb
CHANGED
@@ -2,29 +2,16 @@ module Celluloid
|
|
2
2
|
# Turn concurrent objects into finite state machines
|
3
3
|
# Inspired by Erlang's gen_fsm. See http://www.erlang.org/doc/man/gen_fsm.html
|
4
4
|
module FSM
|
5
|
+
class UnattachedError < StandardError; end # Not attached to an actor
|
6
|
+
|
5
7
|
DEFAULT_STATE = :default # Default state name unless one is explicitly set
|
6
8
|
|
7
9
|
# Included hook to extend class methods
|
8
10
|
def self.included(klass)
|
9
|
-
klass.send :
|
10
|
-
klass.send :extend, ClassMethods
|
11
|
+
klass.send :extend, ClassMethods
|
11
12
|
end
|
12
13
|
|
13
14
|
module ClassMethods
|
14
|
-
# Ensure FSMs transition into the default state after they're initialized
|
15
|
-
def new(*args, &block)
|
16
|
-
fsm = super
|
17
|
-
fsm.transition default_state
|
18
|
-
fsm
|
19
|
-
end
|
20
|
-
|
21
|
-
# Ensure FSMs transition into the default state after they're initialized
|
22
|
-
def new_link(*args, &block)
|
23
|
-
fsm = super
|
24
|
-
fsm.transition default_state
|
25
|
-
fsm
|
26
|
-
end
|
27
|
-
|
28
15
|
# Obtain or set the default state
|
29
16
|
# Passing a state name sets the default state
|
30
17
|
def default_state(new_default = nil)
|
@@ -45,6 +32,7 @@ module Celluloid
|
|
45
32
|
# * to: a state or array of states this state can transition to
|
46
33
|
def state(*args, &block)
|
47
34
|
if args.last.is_a? Hash
|
35
|
+
# Stringify keys :/
|
48
36
|
options = args.pop.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
49
37
|
else
|
50
38
|
options = {}
|
@@ -52,16 +40,29 @@ module Celluloid
|
|
52
40
|
|
53
41
|
args.each do |name|
|
54
42
|
name = name.to_sym
|
43
|
+
default_state name if options['default']
|
55
44
|
states[name] = State.new(name, options['to'], &block)
|
56
45
|
end
|
57
46
|
end
|
58
47
|
end
|
59
48
|
|
49
|
+
attr_reader :actor
|
50
|
+
|
51
|
+
# Be kind and call super if you must redefine initialize
|
52
|
+
def initialize(actor = nil)
|
53
|
+
@state = self.class.default_state
|
54
|
+
@actor = actor
|
55
|
+
end
|
56
|
+
|
60
57
|
# Obtain the current state of the FSM
|
61
|
-
|
62
|
-
|
58
|
+
attr_reader :state
|
59
|
+
|
60
|
+
# Attach this FSM to an actor. This allows FSMs to wait for and initiate
|
61
|
+
# events in the context of a particular actor
|
62
|
+
def attach(actor)
|
63
|
+
@actor = actor
|
63
64
|
end
|
64
|
-
alias_method :
|
65
|
+
alias_method :actor=, :attach
|
65
66
|
|
66
67
|
# Transition to another state
|
67
68
|
# Options:
|
@@ -83,33 +84,30 @@ module Celluloid
|
|
83
84
|
|
84
85
|
new_state = self.class.states[state_name]
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
87
|
+
unless new_state
|
88
|
+
return if state_name == self.class.default_state
|
89
|
+
raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
|
89
90
|
end
|
90
91
|
|
91
|
-
if
|
92
|
-
|
93
|
-
|
92
|
+
if options[:delay]
|
93
|
+
raise UnattachedError, "can't delay unless attached" unless @actor
|
94
|
+
@delayed_transition.cancel if @delayed_transition
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
return @delayed_transition
|
96
|
+
@delayed_transition = @actor.after(options[:delay]) do
|
97
|
+
transition! new_state.name
|
98
|
+
new_state.call(self)
|
101
99
|
end
|
102
100
|
|
103
|
-
|
104
|
-
|
105
|
-
@delayed_transition = nil
|
106
|
-
end
|
101
|
+
return @delayed_transition
|
102
|
+
end
|
107
103
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
|
104
|
+
if defined?(@delayed_transition) and @delayed_transition
|
105
|
+
@delayed_transition.cancel
|
106
|
+
@delayed_transition = nil
|
112
107
|
end
|
108
|
+
|
109
|
+
transition! new_state.name
|
110
|
+
new_state.call(self)
|
113
111
|
end
|
114
112
|
|
115
113
|
# Immediate state transition with no sanity checks. "Dangerous!"
|
data/lib/celluloid/future.rb
CHANGED
@@ -4,56 +4,104 @@ module Celluloid
|
|
4
4
|
# Celluloid::Future objects allow methods and blocks to run in the
|
5
5
|
# background, their values requested later
|
6
6
|
class Future
|
7
|
-
# Create a
|
8
|
-
# the background and its return value obtained later
|
7
|
+
# Create a future bound to a given receiver, or with a block to compute
|
9
8
|
def initialize(*args, &block)
|
10
|
-
@
|
11
|
-
@
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@ready = false
|
11
|
+
@result = nil
|
12
|
+
@forwards = nil
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
if block
|
15
|
+
@call = SyncCall.new(self, :call, args)
|
16
|
+
ThreadPool.get do
|
17
|
+
begin
|
18
|
+
@call.dispatch(block)
|
19
|
+
rescue
|
20
|
+
# Exceptions in blocks will get raised when the value is retrieved
|
21
|
+
ensure
|
22
|
+
ThreadPool.put Thread.current
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
@call = nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Execute the given method in future context
|
31
|
+
def execute(receiver, method, args, block)
|
32
|
+
@mutex.synchronize do
|
33
|
+
raise "already calling" if @call
|
34
|
+
@call = SyncCall.new(self, method, args, block)
|
35
|
+
end
|
36
|
+
|
37
|
+
receiver << @call
|
15
38
|
end
|
16
39
|
|
17
40
|
# Obtain the value for this Future
|
18
41
|
def value
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
42
|
+
ready = result = nil
|
43
|
+
|
44
|
+
begin
|
45
|
+
@mutex.lock
|
46
|
+
raise "no call requested" unless @call
|
47
|
+
|
48
|
+
if @ready
|
49
|
+
ready = true
|
50
|
+
result = @result
|
51
|
+
else
|
52
|
+
case @forwards
|
53
|
+
when Array
|
54
|
+
@forwards << Thread.mailbox
|
55
|
+
when NilClass
|
56
|
+
@forwards = Thread.mailbox
|
57
|
+
else
|
58
|
+
@forwards = [@forwards, Thread.mailbox]
|
59
|
+
end
|
24
60
|
end
|
61
|
+
ensure
|
62
|
+
@mutex.unlock
|
63
|
+
end
|
25
64
|
|
26
|
-
|
65
|
+
unless ready
|
66
|
+
result = Thread.receive do |msg|
|
67
|
+
msg.is_a?(Future::Result) && msg.future == self
|
68
|
+
end
|
27
69
|
end
|
70
|
+
|
71
|
+
result.value
|
28
72
|
end
|
29
73
|
alias_method :call, :value
|
30
74
|
|
31
|
-
#
|
32
|
-
|
75
|
+
# Signal this future with the given result value
|
76
|
+
def signal(value)
|
77
|
+
result = Result.new(value, self)
|
33
78
|
|
34
|
-
|
35
|
-
|
36
|
-
include Celluloid
|
79
|
+
@mutex.synchronize do
|
80
|
+
raise "the future has already happened!" if @ready
|
37
81
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
82
|
+
if @forwards
|
83
|
+
@forwards.is_a?(Array) ? @forwards.each { |f| f << result } : @forwards << result
|
84
|
+
end
|
85
|
+
|
86
|
+
@result = result
|
87
|
+
@ready = true
|
42
88
|
end
|
89
|
+
end
|
90
|
+
alias_method :<<, :signal
|
43
91
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
92
|
+
# Inspect this Celluloid::Future
|
93
|
+
alias_method :inspect, :to_s
|
94
|
+
|
95
|
+
# Wrapper for result values to distinguish them in mailboxes
|
96
|
+
class Result
|
97
|
+
attr_reader :future
|
98
|
+
|
99
|
+
def initialize(result, future)
|
100
|
+
@result, @future = result, future
|
51
101
|
end
|
52
102
|
|
53
103
|
def value
|
54
|
-
|
55
|
-
abort @error if @error
|
56
|
-
@value
|
104
|
+
@result.value
|
57
105
|
end
|
58
106
|
end
|
59
107
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Celluloid
|
2
|
-
#
|
3
|
-
class
|
2
|
+
# Supervise collections of actors as a group
|
3
|
+
class Group
|
4
4
|
include Celluloid
|
5
5
|
trap_exit :restart_supervisor
|
6
6
|
|
@@ -75,4 +75,7 @@ module Celluloid
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
78
|
+
|
79
|
+
# Legacy support for the old name. Going away soon!
|
80
|
+
Application = Group
|
78
81
|
end
|
data/lib/celluloid/mailbox.rb
CHANGED
@@ -21,52 +21,52 @@ module Celluloid
|
|
21
21
|
|
22
22
|
# Add a message to the Mailbox
|
23
23
|
def <<(message)
|
24
|
-
@lock.
|
25
|
-
|
24
|
+
@lock.lock
|
25
|
+
raise MailboxError, "dead recipient" if @dead
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
27
|
+
@messages << message
|
28
|
+
@condition.signal
|
30
29
|
nil
|
30
|
+
ensure @lock.unlock
|
31
31
|
end
|
32
32
|
|
33
33
|
# Add a high-priority system event to the Mailbox
|
34
34
|
def system_event(event)
|
35
|
-
@lock.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
35
|
+
@lock.lock
|
36
|
+
unless @dead # Silently fail if messages are sent to dead actors
|
37
|
+
@messages.unshift event
|
38
|
+
@condition.signal
|
40
39
|
end
|
41
40
|
nil
|
41
|
+
ensure @lock.unlock
|
42
42
|
end
|
43
43
|
|
44
44
|
# Receive a message from the Mailbox
|
45
45
|
def receive(timeout = nil, &block)
|
46
46
|
message = nil
|
47
47
|
|
48
|
-
@lock.
|
49
|
-
|
50
|
-
|
51
|
-
begin
|
52
|
-
message = next_message(&block)
|
48
|
+
@lock.lock
|
49
|
+
raise MailboxError, "attempted to receive from a dead mailbox" if @dead
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
now = Time.now
|
57
|
-
wait_until ||= now + timeout
|
58
|
-
wait_interval = wait_until - now
|
59
|
-
return if wait_interval < 0
|
60
|
-
else
|
61
|
-
wait_interval = nil
|
62
|
-
end
|
51
|
+
begin
|
52
|
+
message = next_message(&block)
|
63
53
|
|
64
|
-
|
54
|
+
unless message
|
55
|
+
if timeout
|
56
|
+
now = Time.now
|
57
|
+
wait_until ||= now + timeout
|
58
|
+
wait_interval = wait_until - now
|
59
|
+
return if wait_interval < 0
|
60
|
+
else
|
61
|
+
wait_interval = nil
|
65
62
|
end
|
66
|
-
|
67
|
-
|
63
|
+
|
64
|
+
@condition.wait(@lock, wait_interval)
|
65
|
+
end
|
66
|
+
end until message
|
68
67
|
|
69
68
|
message
|
69
|
+
ensure @lock.unlock
|
70
70
|
end
|
71
71
|
|
72
72
|
# Retrieve the next message in the mailbox
|
@@ -91,14 +91,14 @@ module Celluloid
|
|
91
91
|
def shutdown
|
92
92
|
messages = nil
|
93
93
|
|
94
|
-
@lock.
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
94
|
+
@lock.lock
|
95
|
+
messages = @messages
|
96
|
+
@messages = []
|
97
|
+
@dead = true
|
99
98
|
|
100
99
|
messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
|
101
100
|
true
|
101
|
+
ensure @lock.unlock
|
102
102
|
end
|
103
103
|
|
104
104
|
# Is the mailbox alive?
|
@@ -108,7 +108,9 @@ module Celluloid
|
|
108
108
|
|
109
109
|
# Cast to an array
|
110
110
|
def to_a
|
111
|
-
@lock.
|
111
|
+
@lock.lock
|
112
|
+
@messages.dup
|
113
|
+
ensure @lock.unlock
|
112
114
|
end
|
113
115
|
|
114
116
|
# Iterate through the mailbox
|
data/lib/celluloid/receivers.rb
CHANGED
@@ -20,7 +20,7 @@ module Celluloid
|
|
20
20
|
end
|
21
21
|
|
22
22
|
@receivers << receiver
|
23
|
-
Task.suspend
|
23
|
+
Task.suspend :receiving
|
24
24
|
end
|
25
25
|
|
26
26
|
# How long to wait until the next timer fires
|
@@ -56,7 +56,7 @@ module Celluloid
|
|
56
56
|
|
57
57
|
# Match a message with this receiver's block
|
58
58
|
def match(message)
|
59
|
-
@block.call(message)
|
59
|
+
@block ? @block.call(message) : true
|
60
60
|
end
|
61
61
|
|
62
62
|
def resume(message = nil)
|
data/lib/celluloid/responses.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Responses to calls
|
3
3
|
class Response
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :call, :value
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(call, value)
|
7
|
+
@call, @value = call, value
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
data/lib/celluloid/signals.rb
CHANGED
@@ -20,7 +20,7 @@ module Celluloid
|
|
20
20
|
@waiting[signal] = [tasks, Task.current]
|
21
21
|
end
|
22
22
|
|
23
|
-
Task.suspend
|
23
|
+
Task.suspend :sigwait
|
24
24
|
end
|
25
25
|
|
26
26
|
# Send a signal to all method calls waiting for the given name
|
@@ -31,20 +31,20 @@ module Celluloid
|
|
31
31
|
case tasks
|
32
32
|
when Array
|
33
33
|
tasks.each { |task| run_task task, value }
|
34
|
+
true if tasks.size > 0
|
34
35
|
when NilClass
|
35
|
-
|
36
|
+
false
|
36
37
|
else
|
37
38
|
run_task tasks, value
|
39
|
+
true
|
38
40
|
end
|
39
|
-
|
40
|
-
value
|
41
41
|
end
|
42
42
|
|
43
43
|
# Run the given task, reporting errors that occur
|
44
44
|
def run_task(task, value)
|
45
45
|
task.resume(value)
|
46
46
|
rescue => ex
|
47
|
-
|
47
|
+
Logger.crash("signaling error", ex)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
data/lib/celluloid/task.rb
CHANGED
@@ -6,39 +6,47 @@ module Celluloid
|
|
6
6
|
class Task
|
7
7
|
class TerminatedError < StandardError; end # kill a running fiber
|
8
8
|
|
9
|
-
attr_reader
|
9
|
+
attr_reader :type
|
10
|
+
attr_accessor :status
|
10
11
|
|
11
12
|
# Obtain the current task
|
12
13
|
def self.current
|
13
|
-
|
14
|
-
raise "not in task scope" unless task
|
15
|
-
task
|
14
|
+
Fiber.current.task or raise "no task for this Fiber"
|
16
15
|
end
|
17
16
|
|
18
17
|
# Suspend the running task, deferring to the scheduler
|
19
|
-
def self.suspend(
|
20
|
-
|
18
|
+
def self.suspend(status)
|
19
|
+
task = Task.current
|
20
|
+
task.status = status
|
21
|
+
|
22
|
+
result = Fiber.yield
|
21
23
|
raise TerminatedError, "task was terminated" if result == TerminatedError
|
24
|
+
task.status = :running
|
25
|
+
|
22
26
|
result
|
23
27
|
end
|
24
28
|
|
25
29
|
# Run the given block within a task
|
26
30
|
def initialize(type)
|
27
|
-
@type
|
31
|
+
@type = type
|
32
|
+
@status = :new
|
28
33
|
|
29
34
|
actor = Thread.current[:actor]
|
30
35
|
mailbox = Thread.current[:mailbox]
|
31
36
|
|
32
37
|
@fiber = Fiber.new do
|
38
|
+
@status = :running
|
33
39
|
Thread.current[:actor] = actor
|
34
40
|
Thread.current[:mailbox] = mailbox
|
35
|
-
|
36
41
|
Fiber.current.task = self
|
42
|
+
actor.tasks << self
|
37
43
|
|
38
44
|
begin
|
39
45
|
yield
|
40
46
|
rescue TerminatedError
|
41
47
|
# Task was explicitly terminated
|
48
|
+
ensure
|
49
|
+
actor.tasks.delete self
|
42
50
|
end
|
43
51
|
end
|
44
52
|
end
|
@@ -67,7 +75,7 @@ module Celluloid
|
|
67
75
|
|
68
76
|
# Nicer string inspect for tasks
|
69
77
|
def inspect
|
70
|
-
"<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @running=#{@fiber.alive?}>"
|
78
|
+
"<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}, @running=#{@fiber.alive?}>"
|
71
79
|
end
|
72
80
|
end
|
73
81
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
# Maintain a thread pool FOR SPEED!!
|
5
|
+
module ThreadPool
|
6
|
+
@pool = []
|
7
|
+
@lock = Mutex.new
|
8
|
+
|
9
|
+
# TODO: should really adjust this based on usage
|
10
|
+
@max_idle = 16
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :max_idle
|
14
|
+
|
15
|
+
# Get a thread from the pool, running the given block
|
16
|
+
def get(&block)
|
17
|
+
@lock.synchronize do
|
18
|
+
if @pool.empty?
|
19
|
+
thread = create
|
20
|
+
else
|
21
|
+
thread = @pool.shift
|
22
|
+
end
|
23
|
+
|
24
|
+
thread[:queue] << block
|
25
|
+
thread
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return a thread to the pool
|
30
|
+
def put(thread)
|
31
|
+
@lock.synchronize do
|
32
|
+
if @pool.size >= @max_idle
|
33
|
+
thread[:queue] << nil
|
34
|
+
else
|
35
|
+
@pool << thread
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a new thread with an associated queue of procs to run
|
41
|
+
def create
|
42
|
+
queue = Queue.new
|
43
|
+
thread = Thread.new do
|
44
|
+
begin
|
45
|
+
while func = queue.pop
|
46
|
+
func.call
|
47
|
+
end
|
48
|
+
rescue Exception => ex
|
49
|
+
Logger.crash("#{self} internal failure", ex)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
thread[:queue] = queue
|
53
|
+
thread
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/celluloid/version.rb
CHANGED
@@ -327,17 +327,21 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
327
327
|
end
|
328
328
|
end
|
329
329
|
|
330
|
+
let(:receiver) { @receiver.new }
|
331
|
+
let(:message) { Object.new }
|
332
|
+
|
333
|
+
it "allows unconditional receive" do
|
334
|
+
receiver.signal_myself(message).should == message
|
335
|
+
end
|
336
|
+
|
330
337
|
it "allows arbitrary selective receive" do
|
331
|
-
|
332
|
-
|
333
|
-
received_obj = receiver.signal_myself(obj) { |o| o == obj }
|
334
|
-
received_obj.should == obj
|
338
|
+
received_obj = receiver.signal_myself(message) { |o| o == message }
|
339
|
+
received_obj.should == message
|
335
340
|
end
|
336
341
|
|
337
342
|
it "times out after the given interval" do
|
338
343
|
interval = 0.1
|
339
344
|
started_at = Time.now
|
340
|
-
receiver = @receiver.new
|
341
345
|
|
342
346
|
receiver.receive(interval) { false }.should be_nil
|
343
347
|
(Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
|
@@ -448,7 +452,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
448
452
|
# an alias for Celluloid::Actor#waiting_tasks
|
449
453
|
tasks = actor.tasks
|
450
454
|
tasks.size.should == 1
|
451
|
-
tasks.
|
455
|
+
tasks.first.status.should == :running
|
452
456
|
|
453
457
|
future = actor.future(:blocking_call)
|
454
458
|
sleep 0.1 # hax! waiting for ^^^ call to actually start
|
@@ -456,14 +460,9 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
456
460
|
tasks = actor.tasks
|
457
461
|
tasks.size.should == 2
|
458
462
|
|
459
|
-
blocking_task =
|
460
|
-
|
461
|
-
|
462
|
-
blocking_task = task
|
463
|
-
break
|
464
|
-
end
|
465
|
-
|
466
|
-
tasks[blocking_task].first.should == :call
|
463
|
+
blocking_task = tasks.find { |t| t.status != :running }
|
464
|
+
blocking_task.should be_a Celluloid::Task
|
465
|
+
blocking_task.status.should == :callwait
|
467
466
|
|
468
467
|
actor.blocker.unblock
|
469
468
|
sleep 0.1 # hax again :(
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: celluloid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70145052114120 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70145052114120
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70145052113520 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,25 +32,35 @@ dependencies:
|
|
32
32
|
version: 2.7.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70145052113520
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: benchmark_suite
|
38
|
+
requirement: &70145052113020 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70145052113020
|
36
47
|
description: Celluloid is a concurrent object framework inspired by the Actor Model
|
37
48
|
email:
|
38
|
-
- tony@
|
49
|
+
- tony.arcieri@gmail.com
|
39
50
|
executables: []
|
40
51
|
extensions: []
|
41
52
|
extra_rdoc_files: []
|
42
53
|
files:
|
43
54
|
- README.md
|
44
55
|
- lib/celluloid/actor.rb
|
45
|
-
- lib/celluloid/actor_pool.rb
|
46
56
|
- lib/celluloid/actor_proxy.rb
|
47
|
-
- lib/celluloid/application.rb
|
48
57
|
- lib/celluloid/calls.rb
|
49
58
|
- lib/celluloid/core_ext.rb
|
50
59
|
- lib/celluloid/events.rb
|
51
60
|
- lib/celluloid/fiber.rb
|
52
61
|
- lib/celluloid/fsm.rb
|
53
62
|
- lib/celluloid/future.rb
|
63
|
+
- lib/celluloid/group.rb
|
54
64
|
- lib/celluloid/links.rb
|
55
65
|
- lib/celluloid/logger.rb
|
56
66
|
- lib/celluloid/mailbox.rb
|
@@ -61,6 +71,7 @@ files:
|
|
61
71
|
- lib/celluloid/signals.rb
|
62
72
|
- lib/celluloid/supervisor.rb
|
63
73
|
- lib/celluloid/task.rb
|
74
|
+
- lib/celluloid/thread_pool.rb
|
64
75
|
- lib/celluloid/timers.rb
|
65
76
|
- lib/celluloid/version.rb
|
66
77
|
- lib/celluloid.rb
|
data/lib/celluloid/actor_pool.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
module Celluloid
|
4
|
-
class Actor
|
5
|
-
# Maintain a thread pool of actors FOR SPEED!!
|
6
|
-
class Pool
|
7
|
-
@pool = []
|
8
|
-
@lock = Mutex.new
|
9
|
-
@max_idle = 16
|
10
|
-
|
11
|
-
class << self
|
12
|
-
attr_accessor :max_idle
|
13
|
-
|
14
|
-
def get(&block)
|
15
|
-
@lock.synchronize do
|
16
|
-
if @pool.empty?
|
17
|
-
thread = create
|
18
|
-
else
|
19
|
-
thread = @pool.shift
|
20
|
-
end
|
21
|
-
|
22
|
-
thread[:queue] << block
|
23
|
-
thread
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def put(thread)
|
28
|
-
@lock.synchronize do
|
29
|
-
if @pool.size >= @max_idle
|
30
|
-
thread[:queue] << nil
|
31
|
-
else
|
32
|
-
@pool << thread
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def create
|
38
|
-
queue = Queue.new
|
39
|
-
thread = Thread.new do
|
40
|
-
begin
|
41
|
-
while func = queue.pop
|
42
|
-
func.call
|
43
|
-
end
|
44
|
-
rescue Exception => ex
|
45
|
-
Logger.crash("#{self} internal failure", ex)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
thread[:queue] = queue
|
49
|
-
thread
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|