kulesa-celluloid 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +116 -0
- data/lib/celluloid/actor.rb +279 -0
- data/lib/celluloid/actor_proxy.rb +95 -0
- data/lib/celluloid/calls.rb +105 -0
- data/lib/celluloid/core_ext.rb +25 -0
- data/lib/celluloid/cpu_counter.rb +16 -0
- data/lib/celluloid/events.rb +26 -0
- data/lib/celluloid/fiber.rb +32 -0
- data/lib/celluloid/fsm.rb +151 -0
- data/lib/celluloid/future.rb +110 -0
- data/lib/celluloid/group.rb +90 -0
- data/lib/celluloid/internal_pool.rb +62 -0
- data/lib/celluloid/links.rb +61 -0
- data/lib/celluloid/logger.rb +53 -0
- data/lib/celluloid/mailbox.rb +134 -0
- data/lib/celluloid/pool.rb +105 -0
- data/lib/celluloid/receivers.rb +70 -0
- data/lib/celluloid/registry.rb +35 -0
- data/lib/celluloid/responses.rb +26 -0
- data/lib/celluloid/rspec.rb +2 -0
- data/lib/celluloid/signals.rb +51 -0
- data/lib/celluloid/supervisor.rb +69 -0
- data/lib/celluloid/task.rb +81 -0
- data/lib/celluloid/thread_handle.rb +35 -0
- data/lib/celluloid/timers.rb +110 -0
- data/lib/celluloid/uuid.rb +38 -0
- data/lib/celluloid/version.rb +4 -0
- data/lib/celluloid/worker.rb +78 -0
- data/lib/celluloid.rb +355 -0
- data/spec/support/actor_examples.rb +565 -0
- data/spec/support/mailbox_examples.rb +52 -0
- metadata +142 -0
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
![Celluloid](https://github.com/celluloid/celluloid/raw/master/logo.png)
|
2
|
+
=========
|
3
|
+
[![Build Status](https://secure.travis-ci.org/celluloid/celluloid.png?branch=master)](http://travis-ci.org/celluloid/celluloid)
|
4
|
+
[![Dependency Status](https://gemnasium.com/celluloid/celluloid.png)](https://gemnasium.com/celluloid/celluloid)
|
5
|
+
|
6
|
+
> "I thought of objects being like biological cells and/or individual
|
7
|
+
> computers on a network, only able to communicate with messages"
|
8
|
+
> _--Alan Kay, creator of Smalltalk, on the meaning of "object oriented programming"_
|
9
|
+
|
10
|
+
Celluloid provides a simple and natural way to build fault-tolerant concurrent
|
11
|
+
programs in Ruby. With Celluloid, you can build systems out of concurrent
|
12
|
+
objects just as easily as you build sequential programs out of regular objects.
|
13
|
+
Recommended for any developer, including novices, Celluloid should help ease
|
14
|
+
your worries about building multithreaded Ruby programs.
|
15
|
+
|
16
|
+
Much of the difficulty with building concurrent programs in Ruby arises because
|
17
|
+
the object-oriented mechanisms for structuring code, such as classes and
|
18
|
+
inheritance, are separate from the concurrency mechanisms, such as threads and
|
19
|
+
locks. Celluloid combines these into a single structure, an active object
|
20
|
+
running within a thread, called an "actor".
|
21
|
+
|
22
|
+
By combining concurrency with object oriented programming, Celluloid frees you
|
23
|
+
up from worry about where to use threads and locks. Celluloid combines them
|
24
|
+
together into a single concurrent object oriented programming model,
|
25
|
+
encapsulating state in concurrent objects and thus avoiding many of the
|
26
|
+
problems associated with multithreaded programming. Celluloid provides many
|
27
|
+
features which make concurrent programming simple, easy, and fun:
|
28
|
+
|
29
|
+
* __Automatic "deadlock-free" synchronization:__ Celluloid uses a concurrent
|
30
|
+
object model which combines method dispatch and thread synchronization.
|
31
|
+
Each actor is a concurrent object running in its own thread, and every method
|
32
|
+
invocation is wrapped in a fiber that can be suspended whenever it calls
|
33
|
+
out to other actors, and resumed when the response is available. This means
|
34
|
+
methods which are waiting for responses from other actors, external messages,
|
35
|
+
or other system events (including I/O with Celluloid::IO) can be suspended
|
36
|
+
and will never block other methods that are ready to run. This won't prevent
|
37
|
+
bugs in Celluloid, bugs in other thread-safe libraries you use, and even
|
38
|
+
certain "dangerous" features of Celluloid from causing your program to
|
39
|
+
deadlock, but in general, programs built with Celluloid will be naturally
|
40
|
+
immune to deadlocks.
|
41
|
+
|
42
|
+
* __Fault-tolerance:__ Celluloid has taken to heart many of Erlang's ideas
|
43
|
+
about fault-tolerance in order to enable self-healing applications.
|
44
|
+
The central idea: have you tried turning it off and on again? Celluloid
|
45
|
+
takes care of rebooting subcomponents of your application when they crash,
|
46
|
+
whether it's a single actor, or large (potentially multi-tiered) groups of
|
47
|
+
actors that are all interdependent. This means rather that worrying about
|
48
|
+
rescuing every last exception, you can just sit back, relax, and let parts
|
49
|
+
of your program crash, knowing Celluloid will automatically reboot them in
|
50
|
+
a clean state. Celluloid provides its own implementation of the core
|
51
|
+
fault-tolerance concepts in Erlang including [linking](https://github.com/celluloid/celluloid/wiki/Linking),
|
52
|
+
[supervisors](https://github.com/celluloid/celluloid/wiki/Supervisors),
|
53
|
+
and [supervision trees](https://github.com/celluloid/celluloid/wiki/Groups).
|
54
|
+
|
55
|
+
* __[Futures](https://github.com/celluloid/celluloid/wiki/futures):__
|
56
|
+
Ever wanted to call a method "in the background" and retrieve the
|
57
|
+
value it returns later? Celluloid futures do just that. It's like
|
58
|
+
calling ahead to a restaurant to place an order, so they can work
|
59
|
+
on preparing your food while you're on your way to pick it up.
|
60
|
+
When you ask for a method's return value, it's returned immediately
|
61
|
+
if the method has already completed, or otherwise the current method is
|
62
|
+
suspended until the value becomes available.
|
63
|
+
|
64
|
+
You can also build distributed systems with Celluloid using its
|
65
|
+
[sister project DCell](https://github.com/celluloid/dcell). Evented IO similar
|
66
|
+
to EventMachine (with a synchronous API) is available through the
|
67
|
+
[Celluloid::IO](https://github.com/celluloid/celluloid-io) library.
|
68
|
+
|
69
|
+
[Please see the Celluloid Wiki](https://github.com/celluloid/celluloid/wiki)
|
70
|
+
for more detailed documentation and usage notes.
|
71
|
+
|
72
|
+
Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
73
|
+
or visit us on IRC at #celluloid on freenode
|
74
|
+
|
75
|
+
### Is it any good?
|
76
|
+
|
77
|
+
[Yes.](http://news.ycombinator.com/item?id=3067434)
|
78
|
+
|
79
|
+
### Is It "Production Ready™"?
|
80
|
+
|
81
|
+
Yes, many users are now running Celluloid in production by using
|
82
|
+
[Sidekiq](https://github.com/mperham/sidekiq)
|
83
|
+
|
84
|
+
Supported Platforms
|
85
|
+
-------------------
|
86
|
+
|
87
|
+
Celluloid works on Ruby 1.9.3, JRuby 1.6, and Rubinius 2.0. JRuby or Rubinius
|
88
|
+
are the preferred platforms as they support true thread-level parallelism when
|
89
|
+
executing Ruby code, whereas MRI/YARV is constrained by a global interpreter
|
90
|
+
lock (GIL) and can only execute one thread at a time.
|
91
|
+
|
92
|
+
Celluloid requires Ruby 1.9 mode on all interpreters. This works out of the
|
93
|
+
box on MRI/YARV, and requires the following flags elsewhere:
|
94
|
+
|
95
|
+
* JRuby: --1.9 command line option, or JRUBY_OPTS=--1.9 environment variable
|
96
|
+
* rbx: -X19 command line option
|
97
|
+
|
98
|
+
Additional Reading
|
99
|
+
------------------
|
100
|
+
|
101
|
+
* [Concurrent Object-Oriented Programming in Python with ATOM](http://python.org/workshops/1997-10/proceedings/atom/):
|
102
|
+
a similar system to Celluloid written in Python
|
103
|
+
|
104
|
+
Contributing to Celluloid
|
105
|
+
-------------------------
|
106
|
+
|
107
|
+
* Fork this repository on github
|
108
|
+
* Make your changes and send me a pull request
|
109
|
+
* If I like them I'll merge them
|
110
|
+
* If I've accepted a patch, feel free to ask for commit access
|
111
|
+
|
112
|
+
License
|
113
|
+
-------
|
114
|
+
|
115
|
+
Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
|
116
|
+
LICENSE.txt for further details.
|
@@ -0,0 +1,279 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Don't do Actor-like things outside Actor scope
|
3
|
+
class NotActorError < StandardError; end
|
4
|
+
|
5
|
+
# Trying to do something to a dead actor
|
6
|
+
class DeadActorError < StandardError; end
|
7
|
+
|
8
|
+
# The caller made an error, not the current actor
|
9
|
+
class AbortError < StandardError
|
10
|
+
attr_reader :cause
|
11
|
+
|
12
|
+
def initialize(cause)
|
13
|
+
@cause = cause
|
14
|
+
super "caused by #{cause.inspect}: #{cause.to_s}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Actors are Celluloid's concurrency primitive. They're implemented as
|
19
|
+
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
20
|
+
# messages.
|
21
|
+
class Actor
|
22
|
+
extend Registry
|
23
|
+
attr_reader :subject, :proxy, :tasks, :links, :mailbox, :thread, :name
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Obtain the current actor
|
27
|
+
def current
|
28
|
+
actor = Thread.current[:actor]
|
29
|
+
raise NotActorError, "not in actor scope" unless actor
|
30
|
+
actor.proxy
|
31
|
+
end
|
32
|
+
|
33
|
+
# Obtain the name of the current actor
|
34
|
+
def name
|
35
|
+
actor = Thread.current[:actor]
|
36
|
+
raise NotActorError, "not in actor scope" unless actor
|
37
|
+
actor.name
|
38
|
+
end
|
39
|
+
|
40
|
+
# Invoke a method on the given actor via its mailbox
|
41
|
+
def call(mailbox, meth, *args, &block)
|
42
|
+
call = SyncCall.new(Thread.mailbox, meth, args, block)
|
43
|
+
|
44
|
+
begin
|
45
|
+
mailbox << call
|
46
|
+
rescue MailboxError
|
47
|
+
raise DeadActorError, "attempted to call a dead actor"
|
48
|
+
end
|
49
|
+
|
50
|
+
if Celluloid.actor? and not Celluloid.exclusive?
|
51
|
+
# The current task will be automatically resumed when we get a response
|
52
|
+
Task.suspend(:callwait).value
|
53
|
+
else
|
54
|
+
# Otherwise we're inside a normal thread, so block
|
55
|
+
response = Thread.mailbox.receive do |msg|
|
56
|
+
msg.respond_to?(:call) and msg.call == call
|
57
|
+
end
|
58
|
+
|
59
|
+
response.value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Invoke a method asynchronously on an actor via its mailbox
|
64
|
+
def async(mailbox, meth, *args, &block)
|
65
|
+
begin
|
66
|
+
mailbox << AsyncCall.new(Thread.mailbox, meth, args, block)
|
67
|
+
rescue MailboxError
|
68
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
69
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
70
|
+
# async calls should find other ways to deal with actors dying
|
71
|
+
# during an async call (i.e. linking/supervisors)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Call a method asynchronously and retrieve its value later
|
76
|
+
def future(mailbox, meth, *args, &block)
|
77
|
+
future = Future.new
|
78
|
+
future.execute(mailbox, meth, args, block)
|
79
|
+
future
|
80
|
+
end
|
81
|
+
|
82
|
+
# Obtain all running actors in the system
|
83
|
+
def all
|
84
|
+
actors = []
|
85
|
+
Thread.list.each do |t|
|
86
|
+
actor = t[:actor]
|
87
|
+
actors << actor.proxy if actor
|
88
|
+
end
|
89
|
+
actors
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Wrap the given subject with an Actor
|
94
|
+
def initialize(subject)
|
95
|
+
@subject = subject
|
96
|
+
@mailbox = subject.class.mailbox_factory
|
97
|
+
@tasks = Set.new
|
98
|
+
@links = Links.new
|
99
|
+
@signals = Signals.new
|
100
|
+
@receivers = Receivers.new
|
101
|
+
@timers = Timers.new
|
102
|
+
@running = true
|
103
|
+
@exclusive = false
|
104
|
+
@name = nil
|
105
|
+
|
106
|
+
@thread = ThreadHandle.new do
|
107
|
+
Thread.current[:actor] = self
|
108
|
+
Thread.current[:mailbox] = @mailbox
|
109
|
+
run
|
110
|
+
end
|
111
|
+
|
112
|
+
@proxy = ActorProxy.new(self)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Is this actor running in exclusive mode?
|
116
|
+
def exclusive?
|
117
|
+
@exclusive
|
118
|
+
end
|
119
|
+
|
120
|
+
# Execute a code block in exclusive mode.
|
121
|
+
def exclusive
|
122
|
+
@exclusive = true
|
123
|
+
yield
|
124
|
+
ensure
|
125
|
+
@exclusive = false
|
126
|
+
end
|
127
|
+
|
128
|
+
# Terminate this actor
|
129
|
+
def terminate
|
130
|
+
@running = false
|
131
|
+
end
|
132
|
+
|
133
|
+
# Send a signal with the given name to all waiting methods
|
134
|
+
def signal(name, value = nil)
|
135
|
+
@signals.send name, value
|
136
|
+
end
|
137
|
+
|
138
|
+
# Wait for the given signal
|
139
|
+
def wait(name)
|
140
|
+
@signals.wait name
|
141
|
+
end
|
142
|
+
|
143
|
+
# Receive an asynchronous message
|
144
|
+
def receive(timeout = nil, &block)
|
145
|
+
@receivers.receive(timeout, &block)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Run the actor loop
|
149
|
+
def run
|
150
|
+
begin
|
151
|
+
while @running
|
152
|
+
begin
|
153
|
+
message = @mailbox.receive(timeout)
|
154
|
+
rescue ExitEvent => exit_event
|
155
|
+
Task.new(:exit_handler) { handle_exit_event exit_event }.resume
|
156
|
+
retry
|
157
|
+
rescue NamingRequest => ex
|
158
|
+
@name = ex.name
|
159
|
+
retry
|
160
|
+
rescue TerminationRequest
|
161
|
+
break
|
162
|
+
end
|
163
|
+
|
164
|
+
if message
|
165
|
+
handle_message message
|
166
|
+
else
|
167
|
+
# No message indicates a timeout
|
168
|
+
@timers.fire
|
169
|
+
@receivers.fire_timers
|
170
|
+
end
|
171
|
+
end
|
172
|
+
rescue MailboxShutdown
|
173
|
+
# If the mailbox detects shutdown, exit the actor
|
174
|
+
end
|
175
|
+
|
176
|
+
shutdown
|
177
|
+
rescue Exception => ex
|
178
|
+
handle_crash(ex)
|
179
|
+
raise unless ex.is_a? StandardError
|
180
|
+
end
|
181
|
+
|
182
|
+
# How long to wait until the next timer fires
|
183
|
+
def timeout
|
184
|
+
i1 = @timers.wait_interval
|
185
|
+
i2 = @receivers.wait_interval
|
186
|
+
|
187
|
+
if i1 and i2
|
188
|
+
i1 < i2 ? i1 : i2
|
189
|
+
elsif i1
|
190
|
+
i1
|
191
|
+
else
|
192
|
+
i2
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Schedule a block to run at the given time
|
197
|
+
def after(interval)
|
198
|
+
@timers.add(interval) do
|
199
|
+
Task.new(:timer) { yield }.resume
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Schedule a block to run at the given time
|
204
|
+
def every(interval)
|
205
|
+
@timers.add(interval, true) do
|
206
|
+
Task.new(:timer) { yield }.resume
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Sleep for the given amount of time
|
211
|
+
def sleep(interval)
|
212
|
+
if Celluloid.exclusive?
|
213
|
+
Kernel.sleep(interval)
|
214
|
+
else
|
215
|
+
task = Task.current
|
216
|
+
@timers.add(interval) { task.resume }
|
217
|
+
Task.suspend :sleeping
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Handle an incoming message
|
222
|
+
def handle_message(message)
|
223
|
+
case message
|
224
|
+
when Call
|
225
|
+
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
226
|
+
when Response
|
227
|
+
message.call.task.resume message
|
228
|
+
else
|
229
|
+
@receivers.handle_message(message)
|
230
|
+
end
|
231
|
+
message
|
232
|
+
end
|
233
|
+
|
234
|
+
# Handle exit events received by this actor
|
235
|
+
def handle_exit_event(exit_event)
|
236
|
+
exit_handler = @subject.class.exit_handler
|
237
|
+
if exit_handler
|
238
|
+
return @subject.send(exit_handler, exit_event.actor, exit_event.reason)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Reraise exceptions from linked actors
|
242
|
+
# If no reason is given, actor terminated cleanly
|
243
|
+
raise exit_event.reason if exit_event.reason
|
244
|
+
end
|
245
|
+
|
246
|
+
# Handle any exceptions that occur within a running actor
|
247
|
+
def handle_crash(exception)
|
248
|
+
Logger.crash("#{@subject.class} crashed!", exception)
|
249
|
+
shutdown ExitEvent.new(@proxy, exception)
|
250
|
+
rescue => ex
|
251
|
+
Logger.crash("#{@subject.class}: ERROR HANDLER CRASHED!", ex)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Handle cleaning up this actor after it exits
|
255
|
+
def shutdown(exit_event = ExitEvent.new(@proxy))
|
256
|
+
run_finalizer
|
257
|
+
cleanup exit_event
|
258
|
+
ensure
|
259
|
+
Thread.current[:actor] = nil
|
260
|
+
Thread.current[:mailbox] = nil
|
261
|
+
end
|
262
|
+
|
263
|
+
# Run the user-defined finalizer, if one is set
|
264
|
+
def run_finalizer
|
265
|
+
@subject.finalize if @subject.respond_to? :finalize
|
266
|
+
rescue => ex
|
267
|
+
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
268
|
+
end
|
269
|
+
|
270
|
+
# Clean up after this actor
|
271
|
+
def cleanup(exit_event)
|
272
|
+
@mailbox.shutdown
|
273
|
+
@links.send_event exit_event
|
274
|
+
tasks.each { |task| task.terminate }
|
275
|
+
rescue => ex
|
276
|
+
Logger.crash("#{@subject.class}: CLEANUP CRASHED!", ex)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# A proxy object returned from Celluloid::Actor.spawn/spawn_link which
|
3
|
+
# dispatches calls and casts to normal Ruby objects which are running inside
|
4
|
+
# of their own threads.
|
5
|
+
class ActorProxy
|
6
|
+
attr_reader :mailbox
|
7
|
+
|
8
|
+
def initialize(actor)
|
9
|
+
@mailbox, @thread, @klass = actor.mailbox, actor.thread, actor.subject.class.to_s
|
10
|
+
|
11
|
+
# Cache "unbanged" versions of methods, e.g. :foobar! => :foobar
|
12
|
+
@unbanged_methods = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def _send_(meth, *args, &block)
|
16
|
+
Actor.call @mailbox, :__send__, meth, *args, &block
|
17
|
+
end
|
18
|
+
|
19
|
+
def class
|
20
|
+
Actor.call @mailbox, :__send__, :class
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
Actor.call @mailbox, :name
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_a?(klass)
|
28
|
+
Actor.call @mailbox, :is_a?, klass
|
29
|
+
end
|
30
|
+
|
31
|
+
def kind_of?(klass)
|
32
|
+
Actor.call @mailbox, :kind_of?, klass
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to?(meth)
|
36
|
+
Actor.call @mailbox, :respond_to?, meth
|
37
|
+
end
|
38
|
+
|
39
|
+
def methods(include_ancestors = true)
|
40
|
+
Actor.call @mailbox, :methods, include_ancestors
|
41
|
+
end
|
42
|
+
|
43
|
+
def alive?
|
44
|
+
@mailbox.alive?
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
Actor.call @mailbox, :to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
Actor.call @mailbox, :inspect
|
53
|
+
rescue DeadActorError
|
54
|
+
"#<Celluloid::Actor(#{@klass}) dead>"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Make an asynchronous call to an actor, for those who don't like the
|
58
|
+
# predicate syntax. TIMTOWTDI!
|
59
|
+
def async(method_name, *args, &block)
|
60
|
+
Actor.async @mailbox, method_name, *args, &block
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create a Celluloid::Future which calls a given method
|
64
|
+
def future(method_name, *args, &block)
|
65
|
+
Actor.future @mailbox, method_name, *args, &block
|
66
|
+
end
|
67
|
+
|
68
|
+
# Terminate the associated actor
|
69
|
+
def terminate
|
70
|
+
terminate!
|
71
|
+
Thread.pass while alive?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Terminate the associated actor asynchronously
|
75
|
+
def terminate!
|
76
|
+
raise DeadActorError, "actor already terminated" unless alive?
|
77
|
+
@mailbox.system_event TerminationRequest.new
|
78
|
+
end
|
79
|
+
|
80
|
+
# method_missing black magic to call bang predicate methods asynchronously
|
81
|
+
def method_missing(meth, *args, &block)
|
82
|
+
# bang methods are async calls
|
83
|
+
if meth.match(/!$/)
|
84
|
+
# This operation is idempotent and therefore thread-safe
|
85
|
+
# The worst case is that the string transformation on the right will
|
86
|
+
# run multiple times and make a little more work for the GC
|
87
|
+
meth = @unbanged_methods[meth] ||= meth.to_s.sub(/!$/, '').to_sym
|
88
|
+
|
89
|
+
Actor.async @mailbox, meth, *args, &block
|
90
|
+
else
|
91
|
+
Actor.call @mailbox, meth, *args, &block
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Calls represent requests to an actor
|
3
|
+
class Call
|
4
|
+
attr_reader :caller, :method, :arguments, :block
|
5
|
+
|
6
|
+
def initialize(caller, method, arguments = [], block = nil)
|
7
|
+
@caller, @method, @arguments, @block = caller, method, arguments, block
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_signature(obj)
|
11
|
+
unless obj.respond_to? @method
|
12
|
+
raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
arity = obj.method(@method).arity
|
17
|
+
rescue NameError
|
18
|
+
# If the object claims it responds to a method, but it doesn't exist,
|
19
|
+
# then we have to assume method_missing will do what it says
|
20
|
+
@arguments.unshift(@method)
|
21
|
+
@method = :method_missing
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
if arity >= 0
|
26
|
+
if arguments.size != arity
|
27
|
+
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{arity})"
|
28
|
+
end
|
29
|
+
elsif arity < -1
|
30
|
+
mandatory_args = -arity - 1
|
31
|
+
if arguments.size < mandatory_args
|
32
|
+
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{mandatory_args})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Synchronous calls wait for a response
|
39
|
+
class SyncCall < Call
|
40
|
+
attr_reader :task
|
41
|
+
|
42
|
+
def initialize(caller, method, arguments = [], block = nil, task = Fiber.current.task)
|
43
|
+
super(caller, method, arguments, block)
|
44
|
+
@task = task
|
45
|
+
end
|
46
|
+
|
47
|
+
def dispatch(obj)
|
48
|
+
begin
|
49
|
+
check_signature(obj)
|
50
|
+
rescue => ex
|
51
|
+
respond ErrorResponse.new(self, AbortError.new(ex))
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
result = obj.send @method, *@arguments, &@block
|
57
|
+
rescue Exception => exception
|
58
|
+
# Exceptions that occur during synchronous calls are reraised in the
|
59
|
+
# context of the caller
|
60
|
+
respond ErrorResponse.new(self, exception)
|
61
|
+
|
62
|
+
if exception.is_a? AbortError
|
63
|
+
# Aborting indicates a protocol error on the part of the caller
|
64
|
+
# It should crash the caller, but the exception isn't reraised
|
65
|
+
return
|
66
|
+
else
|
67
|
+
# Otherwise, it's a bug in this actor and should be reraised
|
68
|
+
raise exception
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
respond SuccessResponse.new(self, result)
|
73
|
+
end
|
74
|
+
|
75
|
+
def cleanup
|
76
|
+
exception = DeadActorError.new("attempted to call a dead actor")
|
77
|
+
respond ErrorResponse.new(self, exception)
|
78
|
+
end
|
79
|
+
|
80
|
+
def respond(message)
|
81
|
+
@caller << message
|
82
|
+
rescue MailboxError
|
83
|
+
# It's possible the caller exited or crashed before we could send a
|
84
|
+
# response to them.
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Asynchronous calls don't wait for a response
|
89
|
+
class AsyncCall < Call
|
90
|
+
def dispatch(obj)
|
91
|
+
begin
|
92
|
+
check_signature(obj)
|
93
|
+
rescue Exception => ex
|
94
|
+
Logger.crash("#{obj.class}: async call `#{@method}' failed!", ex)
|
95
|
+
return
|
96
|
+
end
|
97
|
+
|
98
|
+
obj.send(@method, *@arguments, &@block)
|
99
|
+
rescue AbortError => ex
|
100
|
+
# Swallow aborted async calls, as they indicate the caller made a mistake
|
101
|
+
Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'celluloid/fiber'
|
2
|
+
|
3
|
+
# Monkeypatch Thread to allow lazy access to its Celluloid::Mailbox
|
4
|
+
class Thread
|
5
|
+
attr_accessor :uuid_counter, :uuid_limit
|
6
|
+
|
7
|
+
# Retrieve the mailbox for the current thread or lazily initialize it
|
8
|
+
def self.mailbox
|
9
|
+
current[:mailbox] ||= Celluloid::Mailbox.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Receive a message either as an actor or through the local mailbox
|
13
|
+
def self.receive(timeout = nil, &block)
|
14
|
+
if Celluloid.actor?
|
15
|
+
Celluloid.receive(timeout, &block)
|
16
|
+
else
|
17
|
+
mailbox.receive(timeout, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Fiber
|
23
|
+
# Celluloid::Task associated with this Fiber
|
24
|
+
attr_accessor :task
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
module CPUCounter
|
5
|
+
case RbConfig::CONFIG['host_os'][/^[A-Za-z]+/]
|
6
|
+
when 'darwin'
|
7
|
+
@cores = Integer(`sysctl hw.ncpu`[/\d+/])
|
8
|
+
when 'linux'
|
9
|
+
@cores = File.read("/proc/cpuinfo").scan(/core id\s+: \d+/).uniq.size
|
10
|
+
else
|
11
|
+
@cores = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cores; @cores; end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Exceptional system events which need to be processed out of band
|
3
|
+
class SystemEvent < Exception; end
|
4
|
+
|
5
|
+
# An actor has exited for the given reason
|
6
|
+
class ExitEvent < SystemEvent
|
7
|
+
attr_reader :actor, :reason
|
8
|
+
|
9
|
+
def initialize(actor, reason = nil)
|
10
|
+
@actor, @reason = actor, reason
|
11
|
+
super reason.to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Name an actor at the time it's registered
|
16
|
+
class NamingRequest < SystemEvent
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def initialize(name)
|
20
|
+
@name = name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Request for an actor to terminate
|
25
|
+
class TerminationRequest < SystemEvent; end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Fibers are hard... 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?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
|
28
|
+
raise LoadError, "Celluloid requires Rubinius 1.9 mode. Please pass the -X19 flag."
|
29
|
+
else
|
30
|
+
raise ex
|
31
|
+
end
|
32
|
+
end
|