celluloid 0.5.0 → 0.6.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 +36 -2
- data/lib/celluloid.rb +62 -2
- data/lib/celluloid/actor.rb +113 -86
- data/lib/celluloid/actor_pool.rb +9 -9
- data/lib/celluloid/actor_proxy.rb +13 -67
- data/lib/celluloid/application.rb +78 -0
- data/lib/celluloid/calls.rb +28 -22
- data/lib/celluloid/fibers_are_hard.rb +33 -0
- data/lib/celluloid/io.rb +1 -27
- data/lib/celluloid/io/mailbox.rb +2 -2
- data/lib/celluloid/io/reactor.rb +1 -1
- data/lib/celluloid/linking.rb +1 -1
- data/lib/celluloid/mailbox.rb +6 -1
- data/lib/celluloid/receivers.rb +36 -0
- data/lib/celluloid/responses.rb +14 -4
- data/lib/celluloid/signals.rb +3 -1
- data/lib/celluloid/supervisor.rb +24 -11
- data/lib/celluloid/version.rb +1 -1
- metadata +27 -24
- data/lib/celluloid/io/actor.rb +0 -10
data/README.md
CHANGED
@@ -357,13 +357,43 @@ send them a value in the process:
|
|
357
357
|
end
|
358
358
|
end
|
359
359
|
|
360
|
-
The wait_for_signal method in turn calls a method called "wait". Wait suspends
|
360
|
+
The #wait_for_signal method in turn calls a method called "wait". Wait suspends
|
361
361
|
the running method until another method of the same object calls the "signal"
|
362
362
|
method with the same label.
|
363
363
|
|
364
|
-
The send_signal method of this class does just that, signaling "ponycopter"
|
364
|
+
The #send_signal method of this class does just that, signaling "ponycopter"
|
365
365
|
with the given value. This value is returned from the original wait call.
|
366
366
|
|
367
|
+
Protocol Interaction
|
368
|
+
--------------------
|
369
|
+
|
370
|
+
The asynchronous message protocol Celluloid uses can be used directly to add
|
371
|
+
new behavior to actors.
|
372
|
+
|
373
|
+
To send a raw asynchronous message to an actor, use:
|
374
|
+
|
375
|
+
actor.mailbox << MyMessage.new
|
376
|
+
|
377
|
+
Methods can wait on incoming MyMessage objects using the #receive method:
|
378
|
+
|
379
|
+
class MyActor
|
380
|
+
def initialize
|
381
|
+
wait_for_my_messages!
|
382
|
+
end
|
383
|
+
|
384
|
+
def wait_for_my_messages
|
385
|
+
loop do
|
386
|
+
message = receive { |msg| msg.is_a? MyMessage }
|
387
|
+
puts "Got a MyMessage: #{message.inspect}"
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
The #receive method takes a block, and yields any incoming messages which are
|
393
|
+
received by the current actor to the block, waiting for the block to return
|
394
|
+
true. Calls to #receive sleep until a message is received which makes the
|
395
|
+
block return true, at which point the matching message is returned.
|
396
|
+
|
367
397
|
Handling I/O with Celluloid::IO
|
368
398
|
-------------------------------
|
369
399
|
|
@@ -456,6 +486,10 @@ Here are a few rules you can follow to keep this from happening:
|
|
456
486
|
using Fibers, and why can't it be solved by a block? If you've got a really
|
457
487
|
good reason and you're feeling lucky, knock yourself out.
|
458
488
|
|
489
|
+
5. If you need to mock the behaviour of an Actor, you should mock its subject
|
490
|
+
rather than the proxy itself (#actor_subject). This ensures that any time
|
491
|
+
the subject calls methods on self, they will also be appropriately mocked.
|
492
|
+
|
459
493
|
On Thread Safety in Ruby
|
460
494
|
------------------------
|
461
495
|
|
data/lib/celluloid.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'logger'
|
2
|
+
require 'thread'
|
3
|
+
require 'celluloid/fibers_are_hard'
|
2
4
|
|
3
5
|
module Celluloid
|
4
6
|
@logger = Logger.new STDERR
|
@@ -23,6 +25,41 @@ module Celluloid
|
|
23
25
|
|
24
26
|
actor
|
25
27
|
end
|
28
|
+
|
29
|
+
# Receive an asynchronous message
|
30
|
+
def receive(&block)
|
31
|
+
actor = Thread.current[:actor]
|
32
|
+
if actor
|
33
|
+
actor.receive(&block)
|
34
|
+
else
|
35
|
+
Thread.current.mailbox.receive(&block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create a fiber that participates in the Celluloid protocol
|
40
|
+
def fiber(*args)
|
41
|
+
actor = Thread.current[:actor]
|
42
|
+
proxy = Thread.current[:actor_proxy]
|
43
|
+
mailbox = Thread.current[:mailbox]
|
44
|
+
|
45
|
+
Fiber.new do
|
46
|
+
Thread.current[:actor] = actor
|
47
|
+
Thread.current[:actor_proxy] = proxy
|
48
|
+
Thread.current[:mailbox] = mailbox
|
49
|
+
|
50
|
+
yield(*args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Resume a fiber that participates in the Celluloid protocol
|
55
|
+
def resume_fiber(fiber, value = nil)
|
56
|
+
actor = Thread.current[:actor]
|
57
|
+
if actor
|
58
|
+
actor.run_fiber fiber, value
|
59
|
+
else
|
60
|
+
fiber.resume value
|
61
|
+
end
|
62
|
+
end
|
26
63
|
end
|
27
64
|
|
28
65
|
# Class methods added to classes which include Celluloid
|
@@ -64,8 +101,17 @@ module Celluloid
|
|
64
101
|
@exit_handler = callback.to_sym
|
65
102
|
end
|
66
103
|
|
67
|
-
# Obtain the exit handler
|
68
|
-
|
104
|
+
# Obtain the exit handler for this actor
|
105
|
+
attr_reader :exit_handler
|
106
|
+
|
107
|
+
# Configure a custom mailbox factory
|
108
|
+
def use_mailbox(klass = nil, &block)
|
109
|
+
if block
|
110
|
+
define_method(:mailbox_factory, &block)
|
111
|
+
else
|
112
|
+
define_method(:mailbox_factory) { klass.new }
|
113
|
+
end
|
114
|
+
end
|
69
115
|
end
|
70
116
|
|
71
117
|
#
|
@@ -112,6 +158,18 @@ module Celluloid
|
|
112
158
|
Celluloid.current_actor
|
113
159
|
end
|
114
160
|
|
161
|
+
# Obtain the Ruby object the actor is wrapping. This should ONLY be used
|
162
|
+
# for a limited set of use cases like runtime metaprogramming. Interacting
|
163
|
+
# directly with the wrapped object foregoes any kind of thread safety that
|
164
|
+
# Celluloid would ordinarily provide you, and the object is guaranteed to
|
165
|
+
# be shared with at least the actor thread. Tread carefully.
|
166
|
+
def wrapped_object; self; end
|
167
|
+
|
168
|
+
# Receive an asynchronous message via the actor protocol
|
169
|
+
def receive(&block)
|
170
|
+
Celluloid.receive(&block)
|
171
|
+
end
|
172
|
+
|
115
173
|
# Perform a blocking or computationally intensive action inside an
|
116
174
|
# asynchronous thread pool, allowing the caller to continue processing other
|
117
175
|
# messages in its mailbox in the meantime
|
@@ -151,6 +209,7 @@ require 'celluloid/core_ext'
|
|
151
209
|
require 'celluloid/events'
|
152
210
|
require 'celluloid/linking'
|
153
211
|
require 'celluloid/mailbox'
|
212
|
+
require 'celluloid/receivers'
|
154
213
|
require 'celluloid/registry'
|
155
214
|
require 'celluloid/responses'
|
156
215
|
require 'celluloid/signals'
|
@@ -159,6 +218,7 @@ require 'celluloid/actor'
|
|
159
218
|
require 'celluloid/actor_pool'
|
160
219
|
require 'celluloid/supervisor'
|
161
220
|
require 'celluloid/future'
|
221
|
+
require 'celluloid/application'
|
162
222
|
|
163
223
|
require 'celluloid/io'
|
164
224
|
require 'celluloid/tcp_server'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -1,18 +1,3 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
begin
|
4
|
-
require 'fiber'
|
5
|
-
rescue LoadError => ex
|
6
|
-
if defined? JRUBY_VERSION
|
7
|
-
raise LoadError, "Celluloid requires JRuby 1.9 mode. Please pass the --1.9 flag or set JRUBY_OPTS=--1.9"
|
8
|
-
elsif defined? Rubinius
|
9
|
-
# If we're on Rubinius, we can still work in 1.8 mode
|
10
|
-
Fiber = Rubinius::Fiber
|
11
|
-
else
|
12
|
-
raise ex
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
1
|
module Celluloid
|
17
2
|
# Don't do Actor-like things outside Actor scope
|
18
3
|
class NotActorError < StandardError; end
|
@@ -41,47 +26,67 @@ module Celluloid
|
|
41
26
|
attr_reader :links
|
42
27
|
attr_reader :mailbox
|
43
28
|
|
29
|
+
# Invoke a method on the given actor via its mailbox
|
30
|
+
def self.call(mailbox, meth, *args, &block)
|
31
|
+
our_mailbox = Thread.current.mailbox
|
32
|
+
call = SyncCall.new(our_mailbox, meth, args, block)
|
44
33
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@links = Links.new
|
50
|
-
@signals = Signals.new
|
51
|
-
@proxy = ActorProxy.new(self, @mailbox)
|
52
|
-
@running = true
|
53
|
-
|
54
|
-
@thread = Pool.get
|
55
|
-
@thread[:queue] << proc do
|
56
|
-
initialize_thread_locals
|
57
|
-
run
|
34
|
+
begin
|
35
|
+
mailbox << call
|
36
|
+
rescue MailboxError
|
37
|
+
raise DeadActorError, "attempted to call a dead actor"
|
58
38
|
end
|
59
|
-
end
|
60
39
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
40
|
+
if Celluloid.actor?
|
41
|
+
# Yield to the actor scheduler, which resumes us when we get a response
|
42
|
+
response = Fiber.yield(call)
|
43
|
+
else
|
44
|
+
# Otherwise we're inside a normal thread, so block
|
45
|
+
response = our_mailbox.receive do |msg|
|
46
|
+
msg.is_a? Response and msg.call_id == call.id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
response.value
|
67
51
|
end
|
68
52
|
|
69
|
-
#
|
70
|
-
def
|
71
|
-
Thread.current
|
72
|
-
|
73
|
-
|
53
|
+
# Invoke a method asynchronously on an actor via its mailbox
|
54
|
+
def self.async(mailbox, meth, *args, &block)
|
55
|
+
our_mailbox = Thread.current.mailbox
|
56
|
+
begin
|
57
|
+
mailbox << AsyncCall.new(our_mailbox, meth, args, block)
|
58
|
+
rescue MailboxError
|
59
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
60
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
61
|
+
# async calls should find other ways to deal with actors dying
|
62
|
+
# during an async call (i.e. linking/supervisors)
|
63
|
+
end
|
74
64
|
end
|
75
65
|
|
76
|
-
#
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
66
|
+
# Wrap the given subject with an Actor
|
67
|
+
def initialize(subject)
|
68
|
+
@subject = subject
|
69
|
+
|
70
|
+
if subject.respond_to? :mailbox_factory
|
71
|
+
@mailbox = subject.mailbox_factory
|
72
|
+
else
|
73
|
+
@mailbox = Celluloid::Mailbox.new
|
74
|
+
end
|
75
|
+
|
76
|
+
@links = Links.new
|
77
|
+
@signals = Signals.new
|
78
|
+
@receivers = Receivers.new
|
79
|
+
@proxy = ActorProxy.new(@mailbox)
|
80
|
+
@running = true
|
81
|
+
@pending_calls = {}
|
82
|
+
|
83
|
+
@thread = Pool.get do
|
84
|
+
Thread.current[:actor] = self
|
85
|
+
Thread.current[:actor_proxy] = @proxy
|
86
|
+
Thread.current[:mailbox] = @mailbox
|
87
|
+
|
88
|
+
run
|
89
|
+
end
|
85
90
|
end
|
86
91
|
|
87
92
|
# Is this actor alive?
|
@@ -105,56 +110,72 @@ module Celluloid
|
|
105
110
|
@signals.wait name
|
106
111
|
end
|
107
112
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# Process incoming messages
|
113
|
-
def process_messages
|
114
|
-
pending_calls = {}
|
113
|
+
# Receive an asynchronous message
|
114
|
+
def receive(&block)
|
115
|
+
@receivers.receive(&block)
|
116
|
+
end
|
115
117
|
|
118
|
+
# Run the actor loop
|
119
|
+
def run
|
116
120
|
while @running
|
117
121
|
begin
|
118
122
|
message = @mailbox.receive
|
119
|
-
rescue MailboxShutdown
|
120
|
-
# If the mailbox detects shutdown, exit the actor
|
121
|
-
@running = false
|
122
123
|
rescue ExitEvent => exit_event
|
123
|
-
fiber =
|
124
|
-
|
125
|
-
handle_exit_event exit_event
|
126
|
-
end
|
127
|
-
|
128
|
-
call = fiber.resume
|
129
|
-
pending_calls[call] = fiber if fiber.alive?
|
130
|
-
|
124
|
+
fiber = Celluloid.fiber { handle_exit_event exit_event; nil }
|
125
|
+
run_fiber fiber
|
131
126
|
retry
|
132
127
|
end
|
133
128
|
|
134
|
-
|
135
|
-
when Call
|
136
|
-
fiber = Fiber.new do
|
137
|
-
initialize_thread_locals
|
138
|
-
message.dispatch(@subject)
|
139
|
-
end
|
140
|
-
|
141
|
-
call = fiber.resume
|
142
|
-
pending_calls[call] = fiber if fiber.alive?
|
143
|
-
when Response
|
144
|
-
fiber = pending_calls.delete(message.call)
|
145
|
-
|
146
|
-
if fiber
|
147
|
-
call = fiber.resume message
|
148
|
-
pending_calls[call] = fiber if fiber.alive?
|
149
|
-
end
|
150
|
-
end # unexpected messages are ignored
|
129
|
+
handle_message message
|
151
130
|
end
|
131
|
+
|
132
|
+
cleanup ExitEvent.new(@proxy)
|
133
|
+
rescue MailboxShutdown
|
134
|
+
# If the mailbox detects shutdown, exit the actor
|
135
|
+
@running = false
|
136
|
+
rescue Exception => ex
|
137
|
+
@running = false
|
138
|
+
handle_crash(ex)
|
139
|
+
ensure
|
140
|
+
Pool.put @thread
|
141
|
+
end
|
142
|
+
|
143
|
+
# Run a method, handling when its Fiber is suspended
|
144
|
+
def run_fiber(fiber, value = nil)
|
145
|
+
result = fiber.resume value
|
146
|
+
if result.is_a? Celluloid::Call
|
147
|
+
@pending_calls[result.id] = fiber if fiber.alive?
|
148
|
+
elsif result
|
149
|
+
warning = "non-call returned from fiber: #{result.class}"
|
150
|
+
Celluloid.logger.debug warning if Celluloid.logger
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# Handle an incoming message
|
156
|
+
def handle_message(message)
|
157
|
+
case message
|
158
|
+
when Call
|
159
|
+
fiber = Celluloid.fiber { message.dispatch(@subject); nil }
|
160
|
+
run_fiber fiber
|
161
|
+
when Response
|
162
|
+
fiber = @pending_calls.delete(message.call_id)
|
163
|
+
|
164
|
+
if fiber
|
165
|
+
run_fiber fiber, message
|
166
|
+
else
|
167
|
+
warning = "spurious response to call #{message.call_id}"
|
168
|
+
Celluloid.logger.debug if Celluloid.logger
|
169
|
+
end
|
170
|
+
else
|
171
|
+
@receivers.handle_message(message)
|
172
|
+
end
|
173
|
+
message
|
152
174
|
end
|
153
175
|
|
154
176
|
# Handle exit events received by this actor
|
155
177
|
def handle_exit_event(exit_event)
|
156
|
-
|
157
|
-
exit_handler = klass.exit_handler if klass.respond_to? :exit_handler
|
178
|
+
exit_handler = @subject.class.exit_handler
|
158
179
|
if exit_handler
|
159
180
|
return @subject.send(exit_handler, exit_event.actor, exit_event.reason)
|
160
181
|
end
|
@@ -176,6 +197,12 @@ module Celluloid
|
|
176
197
|
def cleanup(exit_event)
|
177
198
|
@mailbox.shutdown
|
178
199
|
@links.send_event exit_event
|
200
|
+
|
201
|
+
begin
|
202
|
+
@subject.finalize if @subject.respond_to? :finalize
|
203
|
+
rescue Exception => finalizer_exception
|
204
|
+
log_error(finalizer_exception, "#{@subject.class}#finalize crashed!")
|
205
|
+
end
|
179
206
|
end
|
180
207
|
|
181
208
|
# Log errors when an actor crashes
|
data/lib/celluloid/actor_pool.rb
CHANGED
@@ -11,20 +11,23 @@ module Celluloid
|
|
11
11
|
class << self
|
12
12
|
attr_accessor :max_idle
|
13
13
|
|
14
|
-
def get
|
14
|
+
def get(&block)
|
15
15
|
@lock.synchronize do
|
16
16
|
if @pool.empty?
|
17
|
-
create
|
17
|
+
thread = create
|
18
18
|
else
|
19
|
-
@pool.shift
|
19
|
+
thread = @pool.shift
|
20
20
|
end
|
21
|
+
|
22
|
+
thread[:queue] << block
|
23
|
+
thread
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
27
|
def put(thread)
|
25
28
|
@lock.synchronize do
|
26
29
|
if @pool.size >= @max_idle
|
27
|
-
thread
|
30
|
+
thread[:queue] << nil
|
28
31
|
else
|
29
32
|
@pool << thread
|
30
33
|
end
|
@@ -35,13 +38,10 @@ module Celluloid
|
|
35
38
|
queue = Queue.new
|
36
39
|
thread = Thread.new do
|
37
40
|
begin
|
38
|
-
while
|
39
|
-
|
41
|
+
while func = queue.pop
|
42
|
+
func.call
|
40
43
|
end
|
41
44
|
rescue Exception => ex
|
42
|
-
# Rubinius hax
|
43
|
-
raise if defined?(Thread::Die) and ex.is_a? Thread::Die
|
44
|
-
|
45
45
|
message = "Celluloid::Actor::Pool internal failure:\n"
|
46
46
|
message << "#{ex.class}: #{ex.to_s}\n"
|
47
47
|
message << ex.backtrace.join("\n")
|
@@ -3,41 +3,40 @@ module Celluloid
|
|
3
3
|
# dispatches calls and casts to normal Ruby objects which are running inside
|
4
4
|
# of their own threads.
|
5
5
|
class ActorProxy
|
6
|
-
# FIXME: not nearly enough methods are delegated here
|
7
6
|
attr_reader :mailbox
|
8
7
|
|
9
|
-
def initialize(
|
10
|
-
@
|
8
|
+
def initialize(mailbox, klass = "Object")
|
9
|
+
@mailbox, @klass = mailbox, klass
|
11
10
|
end
|
12
11
|
|
13
12
|
def send(meth, *args, &block)
|
14
|
-
|
13
|
+
Actor.call @mailbox, :send, meth, *args, &block
|
15
14
|
end
|
16
15
|
|
17
16
|
def respond_to?(meth)
|
18
|
-
|
17
|
+
Actor.call @mailbox, :respond_to?, meth
|
19
18
|
end
|
20
19
|
|
21
20
|
def methods(include_ancestors = true)
|
22
|
-
|
21
|
+
Actor.call @mailbox, :methods, include_ancestors
|
23
22
|
end
|
24
23
|
|
25
24
|
def alive?
|
26
|
-
@
|
25
|
+
@mailbox.alive?
|
27
26
|
end
|
28
27
|
|
29
28
|
def to_s
|
30
|
-
|
29
|
+
Actor.call @mailbox, :to_s
|
31
30
|
end
|
32
31
|
|
33
32
|
def inspect
|
34
|
-
|
35
|
-
|
33
|
+
"#<Celluloid::Actor(#{@klass}) dead>" unless alive?
|
34
|
+
Actor.call @mailbox, :inspect
|
36
35
|
end
|
37
36
|
|
38
37
|
# Create a Celluloid::Future which calls a given method
|
39
38
|
def future(method_name, *args, &block)
|
40
|
-
Celluloid::Future.new {
|
39
|
+
Celluloid::Future.new { Actor.call @mailbox, method_name, *args, &block }
|
41
40
|
end
|
42
41
|
|
43
42
|
# Terminate the associated actor
|
@@ -61,64 +60,11 @@ module Celluloid
|
|
61
60
|
# bang methods are async calls
|
62
61
|
if meth.to_s.match(/!$/)
|
63
62
|
unbanged_meth = meth.to_s.sub(/!$/, '')
|
64
|
-
|
65
|
-
|
66
|
-
begin
|
67
|
-
@mailbox << AsyncCall.new(our_mailbox, unbanged_meth, args, block)
|
68
|
-
rescue MailboxError
|
69
|
-
# Silently swallow asynchronous calls to dead actors. There's no way
|
70
|
-
# to reliably generate DeadActorErrors for async calls, so users of
|
71
|
-
# async calls should find other ways to deal with actors dying
|
72
|
-
# during an async call (i.e. linking/supervisors)
|
73
|
-
end
|
74
|
-
|
75
|
-
return # casts are async and return immediately
|
63
|
+
Actor.async @mailbox, unbanged_meth, *args, &block
|
64
|
+
return
|
76
65
|
end
|
77
66
|
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
#######
|
82
|
-
private
|
83
|
-
#######
|
84
|
-
|
85
|
-
# Make a synchronous call to the actor we're proxying to
|
86
|
-
def __call(meth, *args, &block)
|
87
|
-
our_mailbox = Thread.current.mailbox
|
88
|
-
call = SyncCall.new(our_mailbox, meth, args, block)
|
89
|
-
|
90
|
-
begin
|
91
|
-
@mailbox << call
|
92
|
-
rescue MailboxError
|
93
|
-
raise DeadActorError, "attempted to call a dead actor"
|
94
|
-
end
|
95
|
-
|
96
|
-
if Celluloid.actor?
|
97
|
-
# Yield to the actor scheduler, which resumes us when we get a response
|
98
|
-
response = Fiber.yield(call)
|
99
|
-
else
|
100
|
-
# Otherwise we're inside a normal thread, so block
|
101
|
-
response = our_mailbox.receive do |msg|
|
102
|
-
msg.is_a? Response and msg.call == call
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
case response
|
107
|
-
when SuccessResponse
|
108
|
-
response.value
|
109
|
-
when ErrorResponse
|
110
|
-
ex = response.value
|
111
|
-
|
112
|
-
if ex.is_a? AbortError
|
113
|
-
# Aborts are caused by caller error, so ensure they capture the
|
114
|
-
# caller's backtrace instead of the receiver's
|
115
|
-
raise ex.cause.class.new(ex.cause.message)
|
116
|
-
else
|
117
|
-
raise ex
|
118
|
-
end
|
119
|
-
else
|
120
|
-
raise "don't know how to handle #{response.class} messages!"
|
121
|
-
end
|
67
|
+
Actor.call @mailbox, meth, *args, &block
|
122
68
|
end
|
123
69
|
end
|
124
70
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Applications describe and manage networks of Celluloid actors
|
3
|
+
class Application
|
4
|
+
include Celluloid
|
5
|
+
trap_exit :restart_supervisor
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Actors or sub-applications to be supervised
|
9
|
+
def supervisables
|
10
|
+
@supervisables ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Start this application (and watch it with a supervisor)
|
14
|
+
alias_method :run!, :supervise
|
15
|
+
|
16
|
+
# Run the application in the foreground with a simple watchdog
|
17
|
+
def run
|
18
|
+
loop do
|
19
|
+
supervisor = run!
|
20
|
+
|
21
|
+
# Take five, toplevel supervisor
|
22
|
+
sleep 5 while supervisor.alive?
|
23
|
+
|
24
|
+
Celluloid.logger.error "!!! Celluloid::Application #{self} crashed. Restarting..."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register an actor class or a sub-application class to be launched and
|
29
|
+
# supervised while this application is running. Available options are:
|
30
|
+
#
|
31
|
+
# * as: register this application in the Celluloid::Actor[] directory
|
32
|
+
# * args: start the actor with the given arguments
|
33
|
+
def supervise(klass, options = {})
|
34
|
+
supervisables << Supervisable.new(klass, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Start the application
|
39
|
+
def initialize
|
40
|
+
@supervisors = {}
|
41
|
+
|
42
|
+
# This is some serious lolcode, but like... start the supervisors for
|
43
|
+
# this application
|
44
|
+
self.class.supervisables.each do |supervisable|
|
45
|
+
supervisor = supervisable.supervise
|
46
|
+
@supervisors[supervisor] = supervisable
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Restart a crashed supervisor
|
51
|
+
def restart_supervisor(supervisor, reason)
|
52
|
+
supervisable = @supervisors.delete supervisor
|
53
|
+
raise "a supervisable went missing. This shouldn't be!" unless supervisable
|
54
|
+
|
55
|
+
supervisor = supervisable.supervise
|
56
|
+
@supervisors[supervisor] = supervisable
|
57
|
+
end
|
58
|
+
|
59
|
+
# A subcomponent of an application to be supervised
|
60
|
+
class Supervisable
|
61
|
+
attr_reader :klass, :as, :args
|
62
|
+
|
63
|
+
def initialize(klass, options = {})
|
64
|
+
@klass = klass
|
65
|
+
|
66
|
+
# Stringify keys :/
|
67
|
+
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
68
|
+
|
69
|
+
@as = options['as']
|
70
|
+
@args = options['args'] || []
|
71
|
+
end
|
72
|
+
|
73
|
+
def supervise
|
74
|
+
Supervisor.new_link(@as, @klass, *@args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/celluloid/calls.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Calls represent requests to an actor
|
3
3
|
class Call
|
4
|
-
attr_reader :caller, :method, :arguments, :block
|
5
|
-
|
4
|
+
attr_reader :id, :caller, :method, :arguments, :block
|
5
|
+
|
6
6
|
def initialize(caller, method, arguments, block)
|
7
|
+
@id = object_id # memoize object ID for serialization
|
7
8
|
@caller, @method, @arguments, @block = caller, method, arguments, block
|
8
9
|
end
|
9
|
-
|
10
|
+
|
10
11
|
def check_signature(obj)
|
11
12
|
unless obj.respond_to? @method
|
12
13
|
raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
|
13
14
|
end
|
14
|
-
|
15
|
+
|
15
16
|
arity = obj.method(@method).arity
|
16
17
|
if arity >= 0
|
17
18
|
if arguments.size != arity
|
@@ -25,24 +26,24 @@ module Celluloid
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
# Synchronous calls wait for a response
|
30
31
|
class SyncCall < Call
|
31
32
|
def dispatch(obj)
|
32
33
|
begin
|
33
34
|
check_signature(obj)
|
34
35
|
rescue Exception => ex
|
35
|
-
respond ErrorResponse.new(
|
36
|
+
respond ErrorResponse.new(@id, AbortError.new(ex))
|
36
37
|
return
|
37
38
|
end
|
38
|
-
|
39
|
+
|
39
40
|
begin
|
40
41
|
result = obj.send @method, *@arguments, &@block
|
41
42
|
rescue Exception => exception
|
42
43
|
# Exceptions that occur during synchronous calls are reraised in the
|
43
44
|
# context of the caller
|
44
|
-
respond ErrorResponse.new(
|
45
|
-
|
45
|
+
respond ErrorResponse.new(@id, exception)
|
46
|
+
|
46
47
|
if exception.is_a? AbortError
|
47
48
|
# Aborting indicates a protocol error on the part of the caller
|
48
49
|
# It should crash the caller, but the exception isn't reraised
|
@@ -52,43 +53,48 @@ module Celluloid
|
|
52
53
|
raise exception
|
53
54
|
end
|
54
55
|
end
|
55
|
-
|
56
|
-
respond SuccessResponse.new(
|
57
|
-
true
|
56
|
+
|
57
|
+
respond SuccessResponse.new(@id, result)
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
def cleanup
|
61
61
|
exception = DeadActorError.new("attempted to call a dead actor")
|
62
|
-
respond ErrorResponse.new(
|
62
|
+
respond ErrorResponse.new(@id, exception)
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
#######
|
66
66
|
private
|
67
67
|
#######
|
68
|
-
|
68
|
+
|
69
69
|
def respond(message)
|
70
70
|
@caller << message
|
71
71
|
rescue MailboxError
|
72
72
|
# It's possible the caller exited or crashed before we could send a
|
73
73
|
# response to them.
|
74
|
-
end
|
74
|
+
end
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
# Asynchronous calls don't wait for a response
|
78
78
|
class AsyncCall < Call
|
79
79
|
def dispatch(obj)
|
80
80
|
begin
|
81
81
|
check_signature(obj)
|
82
82
|
rescue Exception => ex
|
83
|
-
|
83
|
+
log_error ex, "#{obj.class}: async call failed!"
|
84
84
|
return
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
obj.send(@method, *@arguments, &@block)
|
88
88
|
rescue AbortError => ex
|
89
89
|
# Swallow aborted async calls, as they indicate the caller made a mistake
|
90
|
-
obj.
|
90
|
+
obj.log_error ex, "#{obj.class}: async call aborted!"
|
91
|
+
end
|
92
|
+
|
93
|
+
def log_error(ex, message)
|
94
|
+
message << "\n#{ex.class}: #{ex.to_s}\n"
|
95
|
+
message << ex.backtrace.join("\n")
|
96
|
+
Celluloid.logger.error message if Celluloid.logger
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
94
|
-
|
100
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Let's go shopping!
|
2
|
+
begin
|
3
|
+
require 'fiber'
|
4
|
+
rescue LoadError => ex
|
5
|
+
if defined? JRUBY_VERSION
|
6
|
+
if RUBY_VERSION < "1.9.2"
|
7
|
+
raise LoadError, "Celluloid requires JRuby 1.9 mode. Please pass the --1.9 flag or set JRUBY_OPTS=--1.9"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Fibers are broken on JRuby 1.6.5. This works around the issue
|
11
|
+
if JRUBY_VERSION == "1.6.5"
|
12
|
+
require 'jruby'
|
13
|
+
org.jruby.ext.fiber.FiberExtLibrary.new.load(JRuby.runtime, false)
|
14
|
+
class org::jruby::ext::fiber::ThreadFiber
|
15
|
+
field_accessor :state
|
16
|
+
end
|
17
|
+
|
18
|
+
class Fiber
|
19
|
+
def alive?
|
20
|
+
JRuby.reference(self).state != org.jruby.ext.fiber.ThreadFiberState::FINISHED
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
# Just in case subsequent JRuby releases have broken fibers :/
|
25
|
+
raise ex
|
26
|
+
end
|
27
|
+
elsif defined? Rubinius
|
28
|
+
# If we're on Rubinius, we can still work in 1.8 mode
|
29
|
+
Fiber = Rubinius::Fiber
|
30
|
+
else
|
31
|
+
raise ex
|
32
|
+
end
|
33
|
+
end
|
data/lib/celluloid/io.rb
CHANGED
@@ -1,41 +1,15 @@
|
|
1
1
|
require 'celluloid/io/waker'
|
2
2
|
require 'celluloid/io/reactor'
|
3
3
|
require 'celluloid/io/mailbox'
|
4
|
-
require 'celluloid/io/actor'
|
5
4
|
|
6
5
|
module Celluloid
|
7
6
|
# Actors which can run alongside other I/O operations
|
8
7
|
module IO
|
9
8
|
def self.included(klass)
|
10
9
|
klass.send :include, ::Celluloid
|
11
|
-
klass.
|
10
|
+
klass.use_mailbox Celluloid::IO::Mailbox
|
12
11
|
end
|
13
12
|
|
14
|
-
# Class methods added to classes which include Celluloid::IO
|
15
|
-
module ClassMethods
|
16
|
-
# Create a new actor
|
17
|
-
def new(*args, &block)
|
18
|
-
proxy = IO::Actor.new(allocate).proxy
|
19
|
-
proxy.send(:initialize, *args, &block)
|
20
|
-
proxy
|
21
|
-
end
|
22
|
-
|
23
|
-
# Create a new actor and link to the current one
|
24
|
-
def new_link(*args, &block)
|
25
|
-
current_actor = Thread.current[:actor]
|
26
|
-
raise NotActorError, "can't link outside actor context" unless current_actor
|
27
|
-
|
28
|
-
proxy = IO::Actor.new(allocate).proxy
|
29
|
-
current_actor.link proxy
|
30
|
-
proxy.send(:initialize, *args, &block)
|
31
|
-
proxy
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
#
|
36
|
-
# Instance methods
|
37
|
-
#
|
38
|
-
|
39
13
|
# Wait for the given IO object to become readable
|
40
14
|
def wait_readable(io, &block)
|
41
15
|
# Law of demeter be damned!
|
data/lib/celluloid/io/mailbox.rb
CHANGED
@@ -4,7 +4,7 @@ module Celluloid
|
|
4
4
|
module IO
|
5
5
|
# An alternative implementation of Celluloid::Mailbox using Wakers
|
6
6
|
class Mailbox < Celluloid::Mailbox
|
7
|
-
attr_reader :reactor
|
7
|
+
attr_reader :reactor, :waker
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
@messages = []
|
@@ -50,7 +50,7 @@ module Celluloid
|
|
50
50
|
end until message
|
51
51
|
|
52
52
|
message
|
53
|
-
rescue DeadWakerError
|
53
|
+
rescue IOError, DeadWakerError
|
54
54
|
shutdown # force shutdown of the mailbox
|
55
55
|
raise MailboxShutdown, "mailbox shutdown called during receive"
|
56
56
|
end
|
data/lib/celluloid/io/reactor.rb
CHANGED
data/lib/celluloid/linking.rb
CHANGED
data/lib/celluloid/mailbox.rb
CHANGED
@@ -86,6 +86,11 @@ module Celluloid
|
|
86
86
|
true
|
87
87
|
end
|
88
88
|
|
89
|
+
# Is the mailbox alive?
|
90
|
+
def alive?
|
91
|
+
!@dead
|
92
|
+
end
|
93
|
+
|
89
94
|
# Cast to an array
|
90
95
|
def to_a
|
91
96
|
@lock.synchronize { @messages.dup }
|
@@ -98,7 +103,7 @@ module Celluloid
|
|
98
103
|
|
99
104
|
# Inspect the contents of the Mailbox
|
100
105
|
def inspect
|
101
|
-
"
|
106
|
+
"#<#{self.class}:#{object_id.to_s(16)} @messages=[#{map { |m| m.inspect }.join(', ')}]>"
|
102
107
|
end
|
103
108
|
end
|
104
109
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Allow methods to directly interact with the actor protocol
|
3
|
+
class Receivers
|
4
|
+
def initialize
|
5
|
+
@handlers = []
|
6
|
+
end
|
7
|
+
|
8
|
+
# Receive an asynchronous message
|
9
|
+
def receive(&block)
|
10
|
+
raise ArgumentError, "receive must be given a block" unless block
|
11
|
+
|
12
|
+
@handlers << [Fiber.current, block]
|
13
|
+
Fiber.yield
|
14
|
+
end
|
15
|
+
|
16
|
+
# Handle incoming messages
|
17
|
+
def handle_message(message)
|
18
|
+
handler = nil
|
19
|
+
|
20
|
+
@handlers.each_with_index do |(fiber, block), index|
|
21
|
+
if block.call(message)
|
22
|
+
handler = index
|
23
|
+
break
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if handler
|
28
|
+
fiber, _ = @handlers.delete_at handler
|
29
|
+
Celluloid.resume_fiber fiber, message
|
30
|
+
true
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
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_id, :value
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(call_id, value)
|
7
|
+
@call_id, @value = call_id, value
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -12,5 +12,15 @@ module Celluloid
|
|
12
12
|
class SuccessResponse < Response; end
|
13
13
|
|
14
14
|
# Call was aborted due to caller error
|
15
|
-
class ErrorResponse < Response
|
15
|
+
class ErrorResponse < Response
|
16
|
+
def value
|
17
|
+
if super.is_a? AbortError
|
18
|
+
# Aborts are caused by caller error, so ensure they capture the
|
19
|
+
# caller's backtrace instead of the receiver's
|
20
|
+
raise super.cause.class.new(super.cause.message)
|
21
|
+
else
|
22
|
+
raise super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
16
26
|
end
|
data/lib/celluloid/signals.rb
CHANGED
data/lib/celluloid/supervisor.rb
CHANGED
@@ -4,37 +4,50 @@ module Celluloid
|
|
4
4
|
class Supervisor
|
5
5
|
include Celluloid
|
6
6
|
trap_exit :restart_actor
|
7
|
-
|
7
|
+
|
8
8
|
# Retrieve the actor this supervisor is supervising
|
9
9
|
attr_reader :actor
|
10
|
-
|
10
|
+
|
11
11
|
def self.supervise(klass, *args, &block)
|
12
12
|
new(nil, klass, *args, &block)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def self.supervise_as(name, klass, *args, &block)
|
16
16
|
new(name, klass, *args, &block)
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def initialize(name, klass, *args, &block)
|
20
20
|
@name, @klass, @args, @block = name, klass, args, block
|
21
21
|
start_actor
|
22
22
|
end
|
23
|
-
|
24
|
-
def start_actor
|
25
|
-
|
23
|
+
|
24
|
+
def start_actor(start_attempts = 2, sleep_interval = 30)
|
25
|
+
failures = 0
|
26
|
+
|
27
|
+
begin
|
28
|
+
@actor = @klass.new_link(*@args, &@block)
|
29
|
+
rescue
|
30
|
+
failures += 1
|
31
|
+
if failures >= start_attempts
|
32
|
+
failures = 0
|
33
|
+
Celluloid.logger.warn "#{@klass} is crashing on initialize repeatedly, sleeping for #{sleep_interval} seconds"
|
34
|
+
sleep sleep_interval
|
35
|
+
end
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
|
26
39
|
Celluloid::Actor[@name] = @actor if @name
|
27
40
|
end
|
28
|
-
|
41
|
+
|
29
42
|
# When actors die, regardless of the reason, restart them
|
30
43
|
def restart_actor(actor, reason)
|
31
44
|
start_actor
|
32
45
|
end
|
33
|
-
|
46
|
+
|
34
47
|
def inspect
|
35
|
-
str = "
|
48
|
+
str = "#<#{self.class}(#{@klass}):0x#{object_id.to_s(16)}"
|
36
49
|
str << " " << @args.map { |arg| arg.inspect }.join(' ') unless @args.empty?
|
37
50
|
str << ">"
|
38
51
|
end
|
39
52
|
end
|
40
|
-
end
|
53
|
+
end
|
data/lib/celluloid/version.rb
CHANGED
metadata
CHANGED
@@ -1,38 +1,38 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: celluloid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 0.6.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Tony Arcieri
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-11-19 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
|
17
|
-
none: false
|
16
|
+
version_requirements: &2056 !ruby/object:Gem::Requirement
|
18
17
|
requirements:
|
19
18
|
- - ! '>='
|
20
19
|
- !ruby/object:Gem::Version
|
21
20
|
version: '0'
|
22
|
-
|
21
|
+
none: false
|
22
|
+
requirement: *2056
|
23
23
|
prerelease: false
|
24
|
-
|
24
|
+
type: :development
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
|
28
|
-
none: false
|
27
|
+
version_requirements: &2074 !ruby/object:Gem::Requirement
|
29
28
|
requirements:
|
30
29
|
- - ! '>='
|
31
30
|
- !ruby/object:Gem::Version
|
32
31
|
version: 2.7.0
|
33
|
-
|
32
|
+
none: false
|
33
|
+
requirement: *2074
|
34
34
|
prerelease: false
|
35
|
-
|
35
|
+
type: :development
|
36
36
|
description: Celluloid is a concurrent object framework inspired by the Actor Model
|
37
37
|
email:
|
38
38
|
- tony@medioh.com
|
@@ -41,20 +41,20 @@ extensions: []
|
|
41
41
|
extra_rdoc_files: []
|
42
42
|
files:
|
43
43
|
- README.md
|
44
|
+
- lib/celluloid.rb
|
44
45
|
- lib/celluloid/actor.rb
|
45
46
|
- lib/celluloid/actor_pool.rb
|
46
47
|
- lib/celluloid/actor_proxy.rb
|
48
|
+
- lib/celluloid/application.rb
|
47
49
|
- lib/celluloid/calls.rb
|
48
50
|
- lib/celluloid/core_ext.rb
|
49
51
|
- lib/celluloid/events.rb
|
52
|
+
- lib/celluloid/fibers_are_hard.rb
|
50
53
|
- lib/celluloid/future.rb
|
51
|
-
- lib/celluloid/io/actor.rb
|
52
|
-
- lib/celluloid/io/mailbox.rb
|
53
|
-
- lib/celluloid/io/reactor.rb
|
54
|
-
- lib/celluloid/io/waker.rb
|
55
54
|
- lib/celluloid/io.rb
|
56
55
|
- lib/celluloid/linking.rb
|
57
56
|
- lib/celluloid/mailbox.rb
|
57
|
+
- lib/celluloid/receivers.rb
|
58
58
|
- lib/celluloid/registry.rb
|
59
59
|
- lib/celluloid/responses.rb
|
60
60
|
- lib/celluloid/rspec.rb
|
@@ -62,31 +62,34 @@ files:
|
|
62
62
|
- lib/celluloid/supervisor.rb
|
63
63
|
- lib/celluloid/tcp_server.rb
|
64
64
|
- lib/celluloid/version.rb
|
65
|
-
- lib/celluloid.rb
|
65
|
+
- lib/celluloid/io/mailbox.rb
|
66
|
+
- lib/celluloid/io/reactor.rb
|
67
|
+
- lib/celluloid/io/waker.rb
|
66
68
|
homepage: https://github.com/tarcieri/celluloid
|
67
69
|
licenses:
|
68
70
|
- MIT
|
69
|
-
post_install_message:
|
71
|
+
post_install_message:
|
70
72
|
rdoc_options: []
|
71
73
|
require_paths:
|
72
74
|
- lib
|
73
75
|
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
76
|
requirements:
|
76
77
|
- - ! '>='
|
77
78
|
- !ruby/object:Gem::Version
|
78
79
|
version: '0'
|
79
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
80
|
none: false
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
82
|
requirements:
|
82
83
|
- - ! '>='
|
83
84
|
- !ruby/object:Gem::Version
|
84
85
|
version: 1.3.6
|
86
|
+
none: false
|
85
87
|
requirements: []
|
86
|
-
rubyforge_project:
|
87
|
-
rubygems_version: 1.8.
|
88
|
-
signing_key:
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.8.9
|
90
|
+
signing_key:
|
89
91
|
specification_version: 3
|
90
92
|
summary: Celluloid is a concurrent object framework inspired by the Actor Model
|
91
93
|
test_files: []
|
92
|
-
has_rdoc:
|
94
|
+
has_rdoc:
|
95
|
+
...
|