kulesa-celluloid 0.10.2
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 +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
|
+

|
2
|
+
=========
|
3
|
+
[](http://travis-ci.org/celluloid/celluloid)
|
4
|
+
[](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
|