celluloid 0.7.2 → 0.8.0
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/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
|