celluloid 0.12.0 → 0.13.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 -23
- data/lib/celluloid/actor.rb +74 -47
- data/lib/celluloid/autostart.rb +16 -0
- data/lib/celluloid/calls.rb +52 -52
- data/lib/celluloid/condition.rb +70 -0
- data/lib/celluloid/core_ext.rb +1 -1
- data/lib/celluloid/cpu_counter.rb +7 -1
- data/lib/celluloid/fsm.rb +49 -16
- data/lib/celluloid/future.rb +5 -2
- data/lib/celluloid/internal_pool.rb +62 -44
- data/lib/celluloid/legacy.rb +46 -0
- data/lib/celluloid/links.rb +0 -5
- data/lib/celluloid/logger.rb +14 -4
- data/lib/celluloid/logging/incident.rb +21 -0
- data/lib/celluloid/logging/incident_logger.rb +129 -0
- data/lib/celluloid/logging/incident_reporter.rb +48 -0
- data/lib/celluloid/logging/log_event.rb +20 -0
- data/lib/celluloid/logging/ring_buffer.rb +65 -0
- data/lib/celluloid/logging.rb +5 -0
- data/lib/celluloid/mailbox.rb +2 -1
- data/lib/celluloid/method.rb +5 -0
- data/lib/celluloid/pool_manager.rb +21 -8
- data/lib/celluloid/proxies/actor_proxy.rb +11 -11
- data/lib/celluloid/proxies/async_proxy.rb +5 -1
- data/lib/celluloid/responses.rb +8 -6
- data/lib/celluloid/stack_dump.rb +94 -0
- data/lib/celluloid/supervision_group.rb +5 -3
- data/lib/celluloid/system_events.rb +11 -0
- data/lib/celluloid/tasks/task_fiber.rb +17 -9
- data/lib/celluloid/tasks/task_thread.rb +24 -14
- data/lib/celluloid/tasks.rb +59 -0
- data/lib/celluloid/thread_handle.rb +10 -1
- data/lib/celluloid/version.rb +1 -1
- data/lib/celluloid.rb +146 -87
- data/spec/support/actor_examples.rb +242 -114
- data/spec/support/example_actor_class.rb +17 -5
- data/spec/support/task_examples.rb +11 -2
- metadata +58 -14
- data/lib/celluloid/boot.rb +0 -9
- data/lib/celluloid/task.rb +0 -22
data/README.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|

|
|
2
2
|
=========
|
|
3
|
+
[](http://rubygems.org/gems/celluloid)
|
|
3
4
|
[](http://travis-ci.org/celluloid/celluloid)
|
|
4
5
|
[](https://gemnasium.com/celluloid/celluloid)
|
|
6
|
+
[](https://codeclimate.com/github/celluloid/celluloid)
|
|
7
|
+
[](https://coveralls.io/r/celluloid/celluloid)
|
|
5
8
|
|
|
6
9
|
> "I thought of objects being like biological cells and/or individual
|
|
7
10
|
> computers on a network, only able to communicate with messages"
|
|
@@ -44,7 +47,7 @@ features which make concurrent programming simple, easy, and fun:
|
|
|
44
47
|
The central idea: have you tried turning it off and on again? Celluloid
|
|
45
48
|
takes care of rebooting subcomponents of your application when they crash,
|
|
46
49
|
whether it's a single actor, or large (potentially multi-tiered) groups of
|
|
47
|
-
actors that are all interdependent. This means rather
|
|
50
|
+
actors that are all interdependent. This means rather than worrying about
|
|
48
51
|
rescuing every last exception, you can just sit back, relax, and let parts
|
|
49
52
|
of your program crash, knowing Celluloid will automatically reboot them in
|
|
50
53
|
a clean state. Celluloid provides its own implementation of the core
|
|
@@ -63,19 +66,34 @@ features which make concurrent programming simple, easy, and fun:
|
|
|
63
66
|
|
|
64
67
|
You can also build distributed systems with Celluloid using its
|
|
65
68
|
[sister project DCell](https://github.com/celluloid/dcell). Evented IO similar
|
|
66
|
-
to EventMachine (with a synchronous API
|
|
67
|
-
[Celluloid::IO](https://github.com/celluloid/celluloid-io)
|
|
68
|
-
|
|
69
|
-
[Please see the Celluloid Wiki](https://github.com/celluloid/celluloid/wiki)
|
|
70
|
-
for more detailed documentation and usage notes.
|
|
69
|
+
to EventMachine (with a synchronous API instead of callback/deferrable soup)
|
|
70
|
+
is available through the [Celluloid::IO](https://github.com/celluloid/celluloid-io)
|
|
71
|
+
library.
|
|
71
72
|
|
|
72
73
|
Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
|
73
74
|
or visit us on IRC at #celluloid on freenode
|
|
74
75
|
|
|
76
|
+
### Is it any good?
|
|
77
|
+
|
|
78
|
+
[Yes](http://news.ycombinator.com/item?id=3067434)
|
|
79
|
+
|
|
75
80
|
### Is It "Production Ready™"?
|
|
76
81
|
|
|
77
82
|
Yes, many users are now running Celluloid in production by using
|
|
78
|
-
[Sidekiq](
|
|
83
|
+
[Sidekiq](http://sidekiq.org) and [Adhearsion](http://adhearsion.com/)
|
|
84
|
+
|
|
85
|
+
Documentation
|
|
86
|
+
-------------
|
|
87
|
+
|
|
88
|
+
[Please see the Celluloid Wiki](https://github.com/celluloid/celluloid/wiki)
|
|
89
|
+
for more detailed documentation and usage notes.
|
|
90
|
+
|
|
91
|
+
The following API documentation is also available:
|
|
92
|
+
|
|
93
|
+
* [YARD API documentation](http://rubydoc.info/gems/celluloid/frames)
|
|
94
|
+
* [Celluloid module (primary API)](http://rubydoc.info/gems/celluloid/Celluloid)
|
|
95
|
+
* [Celluloid class methods](http://rubydoc.info/gems/celluloid/Celluloid/ClassMethods)
|
|
96
|
+
* [All Celluloid classes](http://rubydoc.info/gems/celluloid/index)
|
|
79
97
|
|
|
80
98
|
Installation
|
|
81
99
|
------------
|
|
@@ -92,25 +110,20 @@ Or install it yourself as:
|
|
|
92
110
|
|
|
93
111
|
$ gem install celluloid
|
|
94
112
|
|
|
95
|
-
Inside of your Ruby program
|
|
96
|
-
|
|
97
|
-
require 'celluloid'
|
|
113
|
+
Inside of your Ruby program, require Celluloid with:
|
|
98
114
|
|
|
99
|
-
|
|
115
|
+
require 'celluloid/autostart'
|
|
100
116
|
|
|
101
117
|
Supported Platforms
|
|
102
118
|
-------------------
|
|
103
119
|
|
|
104
|
-
Celluloid works on Ruby 1.9.3, JRuby 1.6
|
|
105
|
-
are the preferred platforms as they support true thread-level parallelism when
|
|
106
|
-
executing Ruby code, whereas MRI/YARV is constrained by a global interpreter
|
|
107
|
-
lock (GIL) and can only execute one thread at a time.
|
|
120
|
+
Celluloid works on Ruby 1.9.3, 2.0.0, JRuby 1.6+, and Rubinius 2.0.
|
|
108
121
|
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
JRuby or Rubinius are the preferred platforms as they support true thread-level
|
|
123
|
+
parallelism when executing Ruby code, whereas MRI/YARV is constrained by a global
|
|
124
|
+
interpreter lock (GIL) and can only execute one thread at a time.
|
|
111
125
|
|
|
112
|
-
|
|
113
|
-
* rbx: -X19 command line option
|
|
126
|
+
Celluloid requires Ruby 1.9 mode on all interpreters.
|
|
114
127
|
|
|
115
128
|
Additional Reading
|
|
116
129
|
------------------
|
|
@@ -122,12 +135,12 @@ Contributing to Celluloid
|
|
|
122
135
|
-------------------------
|
|
123
136
|
|
|
124
137
|
* Fork this repository on github
|
|
125
|
-
* Make your changes and send
|
|
126
|
-
* If
|
|
127
|
-
* If
|
|
138
|
+
* Make your changes and send us a pull request
|
|
139
|
+
* If we like them we'll merge them
|
|
140
|
+
* If we've accepted a patch, feel free to ask for commit access
|
|
128
141
|
|
|
129
142
|
License
|
|
130
143
|
-------
|
|
131
144
|
|
|
132
|
-
Copyright (c)
|
|
145
|
+
Copyright (c) 2013 Tony Arcieri. Distributed under the MIT License. See
|
|
133
146
|
LICENSE.txt for further details.
|
data/lib/celluloid/actor.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Celluloid
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
LINKING_TIMEOUT = 5 # linking times out after 5 seconds
|
|
24
|
+
OWNER_IVAR = :@celluloid_owner # reference to owning actor
|
|
24
25
|
|
|
25
26
|
# Actors are Celluloid's concurrency primitive. They're implemented as
|
|
26
27
|
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
|
@@ -43,14 +44,14 @@ module Celluloid
|
|
|
43
44
|
|
|
44
45
|
# Obtain the current actor
|
|
45
46
|
def current
|
|
46
|
-
actor = Thread.current[:
|
|
47
|
+
actor = Thread.current[:celluloid_actor]
|
|
47
48
|
raise NotActorError, "not in actor scope" unless actor
|
|
48
49
|
actor.proxy
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
# Obtain the name of the current actor
|
|
52
53
|
def name
|
|
53
|
-
actor = Thread.current[:
|
|
54
|
+
actor = Thread.current[:celluloid_actor]
|
|
54
55
|
raise NotActorError, "not in actor scope" unless actor
|
|
55
56
|
actor.name
|
|
56
57
|
end
|
|
@@ -65,21 +66,7 @@ module Celluloid
|
|
|
65
66
|
raise DeadActorError, "attempted to call a dead actor"
|
|
66
67
|
end
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
# The current task will be automatically resumed when we get a response
|
|
70
|
-
Task.suspend(:callwait).value
|
|
71
|
-
else
|
|
72
|
-
# Otherwise we're inside a normal thread or exclusive, so block
|
|
73
|
-
response = loop do
|
|
74
|
-
message = Thread.mailbox.receive do |msg|
|
|
75
|
-
msg.respond_to?(:call) and msg.call == call
|
|
76
|
-
end
|
|
77
|
-
break message unless message.is_a?(SystemEvent)
|
|
78
|
-
Thread.current[:actor].handle_system_event(message)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
response.value
|
|
82
|
-
end
|
|
69
|
+
Celluloid.suspend(:callwait, call).value
|
|
83
70
|
end
|
|
84
71
|
|
|
85
72
|
# Invoke a method asynchronously on an actor via its mailbox
|
|
@@ -105,7 +92,7 @@ module Celluloid
|
|
|
105
92
|
def all
|
|
106
93
|
actors = []
|
|
107
94
|
Thread.list.each do |t|
|
|
108
|
-
actor = t[:
|
|
95
|
+
actor = t[:celluloid_actor]
|
|
109
96
|
actors << actor.proxy if actor and actor.respond_to?(:proxy)
|
|
110
97
|
end
|
|
111
98
|
actors
|
|
@@ -114,25 +101,25 @@ module Celluloid
|
|
|
114
101
|
# Watch for exit events from another actor
|
|
115
102
|
def monitor(actor)
|
|
116
103
|
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
|
117
|
-
Thread.current[:
|
|
104
|
+
Thread.current[:celluloid_actor].linking_request(actor, :link)
|
|
118
105
|
end
|
|
119
106
|
|
|
120
107
|
# Stop waiting for exit events from another actor
|
|
121
108
|
def unmonitor(actor)
|
|
122
109
|
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
|
123
|
-
Thread.current[:
|
|
110
|
+
Thread.current[:celluloid_actor].linking_request(actor, :unlink)
|
|
124
111
|
end
|
|
125
112
|
|
|
126
113
|
# Link to another actor
|
|
127
114
|
def link(actor)
|
|
128
115
|
monitor actor
|
|
129
|
-
Thread.current[:
|
|
116
|
+
Thread.current[:celluloid_actor].links << actor
|
|
130
117
|
end
|
|
131
118
|
|
|
132
119
|
# Unlink from another actor
|
|
133
120
|
def unlink(actor)
|
|
134
121
|
unmonitor actor
|
|
135
|
-
Thread.current[:
|
|
122
|
+
Thread.current[:celluloid_actor].links.delete actor
|
|
136
123
|
end
|
|
137
124
|
|
|
138
125
|
# Are we monitoring the given actor?
|
|
@@ -142,7 +129,7 @@ module Celluloid
|
|
|
142
129
|
|
|
143
130
|
# Are we bidirectionally linked to the given actor?
|
|
144
131
|
def linked_to?(actor)
|
|
145
|
-
monitoring?(actor) && Thread.current[:
|
|
132
|
+
monitoring?(actor) && Thread.current[:celluloid_actor].links.include?(actor)
|
|
146
133
|
end
|
|
147
134
|
|
|
148
135
|
# Forcibly kill a given actor
|
|
@@ -169,7 +156,7 @@ module Celluloid
|
|
|
169
156
|
@exclusives = options[:exclusive_methods]
|
|
170
157
|
@task_class = options[:task_class] || Celluloid.task_class
|
|
171
158
|
|
|
172
|
-
@tasks =
|
|
159
|
+
@tasks = TaskSet.new
|
|
173
160
|
@links = Links.new
|
|
174
161
|
@signals = Signals.new
|
|
175
162
|
@receivers = Receivers.new
|
|
@@ -179,12 +166,13 @@ module Celluloid
|
|
|
179
166
|
@name = nil
|
|
180
167
|
|
|
181
168
|
@thread = ThreadHandle.new do
|
|
182
|
-
Thread.current[:
|
|
183
|
-
Thread.current[:
|
|
169
|
+
Thread.current[:celluloid_actor] = self
|
|
170
|
+
Thread.current[:celluloid_mailbox] = @mailbox
|
|
184
171
|
run
|
|
185
172
|
end
|
|
186
173
|
|
|
187
|
-
@proxy = ActorProxy.new(self)
|
|
174
|
+
@proxy = (options[:proxy_class] || ActorProxy).new(self)
|
|
175
|
+
@subject.instance_variable_set(OWNER_IVAR, self)
|
|
188
176
|
end
|
|
189
177
|
|
|
190
178
|
# Run the actor loop
|
|
@@ -221,23 +209,31 @@ module Celluloid
|
|
|
221
209
|
|
|
222
210
|
# Execute a code block in exclusive mode.
|
|
223
211
|
def exclusive
|
|
224
|
-
@exclusive
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
212
|
+
if @exclusive
|
|
213
|
+
yield
|
|
214
|
+
else
|
|
215
|
+
begin
|
|
216
|
+
@exclusive = true
|
|
217
|
+
yield
|
|
218
|
+
ensure
|
|
219
|
+
@exclusive = false
|
|
220
|
+
end
|
|
221
|
+
end
|
|
228
222
|
end
|
|
229
223
|
|
|
230
224
|
# Perform a linking request with another actor
|
|
231
225
|
def linking_request(receiver, type)
|
|
232
226
|
exclusive do
|
|
233
227
|
start_time = Time.now
|
|
234
|
-
|
|
235
228
|
receiver.mailbox << LinkingRequest.new(Actor.current, type)
|
|
236
229
|
system_events = []
|
|
230
|
+
|
|
237
231
|
loop do
|
|
238
232
|
wait_interval = start_time + LINKING_TIMEOUT - Time.now
|
|
239
233
|
message = @mailbox.receive(wait_interval) do |msg|
|
|
240
|
-
msg.is_a?(LinkingResponse) &&
|
|
234
|
+
msg.is_a?(LinkingResponse) &&
|
|
235
|
+
msg.actor.mailbox.address == receiver.mailbox.address &&
|
|
236
|
+
msg.type == type
|
|
241
237
|
end
|
|
242
238
|
|
|
243
239
|
case message
|
|
@@ -300,15 +296,25 @@ module Celluloid
|
|
|
300
296
|
@timers.every(interval) { task(:timer, &block) }
|
|
301
297
|
end
|
|
302
298
|
|
|
299
|
+
class Sleeper
|
|
300
|
+
def initialize(timers, interval)
|
|
301
|
+
@timers = timers
|
|
302
|
+
@interval = interval
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def before_suspend(task)
|
|
306
|
+
@timers.after(@interval) { task.resume }
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def wait
|
|
310
|
+
Kernel.sleep(@interval)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
303
314
|
# Sleep for the given amount of time
|
|
304
315
|
def sleep(interval)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
else
|
|
308
|
-
task = Task.current
|
|
309
|
-
@timers.after(interval) { task.resume }
|
|
310
|
-
Task.suspend :sleeping
|
|
311
|
-
end
|
|
316
|
+
sleeper = Sleeper.new(@timers, interval)
|
|
317
|
+
Celluloid.suspend(:sleeping, sleeper)
|
|
312
318
|
end
|
|
313
319
|
|
|
314
320
|
# Handle standard low-priority messages
|
|
@@ -337,11 +343,15 @@ module Celluloid
|
|
|
337
343
|
@name = event.name
|
|
338
344
|
when TerminationRequest
|
|
339
345
|
@running = false
|
|
346
|
+
when SignalConditionRequest
|
|
347
|
+
event.call
|
|
340
348
|
end
|
|
341
349
|
end
|
|
342
350
|
|
|
343
351
|
# Handle exit events received by this actor
|
|
344
352
|
def handle_exit_event(event)
|
|
353
|
+
@links.delete event.actor
|
|
354
|
+
|
|
345
355
|
# Run the exit handler if available
|
|
346
356
|
return @subject.send(@exit_handler, event.actor, event.reason) if @exit_handler
|
|
347
357
|
|
|
@@ -363,14 +373,24 @@ module Celluloid
|
|
|
363
373
|
run_finalizer
|
|
364
374
|
cleanup exit_event
|
|
365
375
|
ensure
|
|
366
|
-
Thread.current[:
|
|
367
|
-
Thread.current[:
|
|
376
|
+
Thread.current[:celluloid_actor] = nil
|
|
377
|
+
Thread.current[:celluloid_mailbox] = nil
|
|
368
378
|
end
|
|
369
379
|
|
|
370
380
|
# Run the user-defined finalizer, if one is set
|
|
371
381
|
def run_finalizer
|
|
372
|
-
|
|
373
|
-
|
|
382
|
+
# FIXME: remove before Celluloid 1.0
|
|
383
|
+
if @subject.respond_to?(:finalize) && @subject.class.finalizer != :finalize
|
|
384
|
+
Logger.warn("DEPRECATION WARNING: #{@subject.class}#finalize is deprecated and will be removed in Celluloid 1.0. " +
|
|
385
|
+
"Define finalizers with '#{@subject.class}.finalizer :callback.'")
|
|
386
|
+
|
|
387
|
+
task(:finalizer, :finalize) { @subject.finalize }
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
finalizer = @subject.class.finalizer
|
|
391
|
+
if finalizer && @subject.respond_to?(finalizer, true)
|
|
392
|
+
task(:finalizer, :finalize) { @subject.__send__(finalizer) }
|
|
393
|
+
end
|
|
374
394
|
rescue => ex
|
|
375
395
|
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
|
376
396
|
end
|
|
@@ -378,7 +398,14 @@ module Celluloid
|
|
|
378
398
|
# Clean up after this actor
|
|
379
399
|
def cleanup(exit_event)
|
|
380
400
|
@mailbox.shutdown
|
|
381
|
-
@links.
|
|
401
|
+
@links.each do |actor|
|
|
402
|
+
begin
|
|
403
|
+
actor.mailbox << exit_event
|
|
404
|
+
rescue MailboxError
|
|
405
|
+
# We're exiting/crashing, they're dead. Give up :(
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
382
409
|
tasks.each { |task| task.terminate }
|
|
383
410
|
rescue => ex
|
|
384
411
|
Logger.crash("#{@subject.class}: CLEANUP CRASHED!", ex)
|
|
@@ -386,10 +413,10 @@ module Celluloid
|
|
|
386
413
|
|
|
387
414
|
# Run a method inside a task unless it's exclusive
|
|
388
415
|
def task(task_type, method_name = nil, &block)
|
|
389
|
-
if @exclusives && (@exclusives == :all || @exclusives.include?(method_name))
|
|
416
|
+
if @exclusives && (@exclusives == :all || (method_name && @exclusives.include?(method_name.to_sym)))
|
|
390
417
|
exclusive { block.call }
|
|
391
418
|
else
|
|
392
|
-
@task_class.new(
|
|
419
|
+
@task_class.new(task_type, &block).resume
|
|
393
420
|
end
|
|
394
421
|
end
|
|
395
422
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'celluloid'
|
|
2
|
+
|
|
3
|
+
Celluloid.boot
|
|
4
|
+
|
|
5
|
+
# Terminate all actors at exit
|
|
6
|
+
at_exit do
|
|
7
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION >= "1.9"
|
|
8
|
+
# workaround for MRI bug losing exit status in at_exit block
|
|
9
|
+
# http://bugs.ruby-lang.org/issues/5218
|
|
10
|
+
exit_status = $!.status if $!.is_a?(SystemExit)
|
|
11
|
+
Celluloid.shutdown
|
|
12
|
+
exit exit_status if exit_status
|
|
13
|
+
else
|
|
14
|
+
Celluloid.shutdown
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/celluloid/calls.rb
CHANGED
|
@@ -7,70 +7,72 @@ module Celluloid
|
|
|
7
7
|
@method, @arguments, @block = method, arguments, block
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
def dispatch(obj)
|
|
11
|
+
obj.public_send(@method, *@arguments, &@block)
|
|
12
|
+
rescue NoMethodError => ex
|
|
13
|
+
# Abort if the caller made a mistake
|
|
14
|
+
raise AbortError.new(ex) unless obj.respond_to? @method
|
|
14
15
|
|
|
16
|
+
# Otherwise something blew up. Crash this actor
|
|
17
|
+
raise
|
|
18
|
+
rescue ArgumentError => ex
|
|
19
|
+
# Abort if the caller made a mistake
|
|
15
20
|
begin
|
|
16
21
|
arity = obj.method(@method).arity
|
|
17
22
|
rescue NameError
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
@arguments.unshift(@method)
|
|
21
|
-
@method = :method_missing
|
|
22
|
-
return
|
|
23
|
+
# In theory this shouldn't happen, but just in case
|
|
24
|
+
raise AbortError.new(ex)
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
if arity >= 0
|
|
26
|
-
if arguments.size != arity
|
|
27
|
-
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{arity})"
|
|
28
|
-
end
|
|
28
|
+
raise AbortError.new(ex) if @arguments.size != arity
|
|
29
29
|
elsif arity < -1
|
|
30
30
|
mandatory_args = -arity - 1
|
|
31
|
-
if arguments.size < mandatory_args
|
|
32
|
-
|
|
31
|
+
raise AbortError.new(ex) if arguments.size < mandatory_args
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Otherwise something blew up. Crash this actor
|
|
35
|
+
raise
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def wait
|
|
39
|
+
loop do
|
|
40
|
+
message = Thread.mailbox.receive do |msg|
|
|
41
|
+
msg.respond_to?(:call) and msg.call == self
|
|
33
42
|
end
|
|
43
|
+
break message unless message.is_a?(SystemEvent)
|
|
44
|
+
Thread.current[:celluloid_actor].handle_system_event(message)
|
|
34
45
|
end
|
|
35
46
|
end
|
|
36
47
|
end
|
|
37
48
|
|
|
38
49
|
# Synchronous calls wait for a response
|
|
39
50
|
class SyncCall < Call
|
|
40
|
-
attr_reader :caller, :task
|
|
51
|
+
attr_reader :caller, :task, :chain_id
|
|
41
52
|
|
|
42
|
-
def initialize(caller, method, arguments = [], block = nil, task = Thread.current[:
|
|
53
|
+
def initialize(caller, method, arguments = [], block = nil, task = Thread.current[:celluloid_task], chain_id = Thread.current[:celluloid_chain_id])
|
|
43
54
|
super(method, arguments, block)
|
|
44
|
-
|
|
45
|
-
@
|
|
55
|
+
|
|
56
|
+
@caller = caller
|
|
57
|
+
@task = task
|
|
58
|
+
@chain_id = chain_id || Celluloid.uuid
|
|
46
59
|
end
|
|
47
60
|
|
|
48
61
|
def dispatch(obj)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
rescue => ex
|
|
52
|
-
respond ErrorResponse.new(self, AbortError.new(ex))
|
|
53
|
-
return
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
begin
|
|
57
|
-
result = obj.send @method, *@arguments, &@block
|
|
58
|
-
rescue Exception => exception
|
|
59
|
-
# Exceptions that occur during synchronous calls are reraised in the
|
|
60
|
-
# context of the caller
|
|
61
|
-
respond ErrorResponse.new(self, exception)
|
|
62
|
-
|
|
63
|
-
if exception.is_a? AbortError
|
|
64
|
-
# Aborting indicates a protocol error on the part of the caller
|
|
65
|
-
# It should crash the caller, but the exception isn't reraised
|
|
66
|
-
return
|
|
67
|
-
else
|
|
68
|
-
# Otherwise, it's a bug in this actor and should be reraised
|
|
69
|
-
raise exception
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
62
|
+
Thread.current[:celluloid_chain_id] = @chain_id
|
|
63
|
+
result = super(obj)
|
|
73
64
|
respond SuccessResponse.new(self, result)
|
|
65
|
+
rescue Exception => ex
|
|
66
|
+
# Exceptions that occur during synchronous calls are reraised in the
|
|
67
|
+
# context of the caller
|
|
68
|
+
respond ErrorResponse.new(self, ex)
|
|
69
|
+
|
|
70
|
+
# Aborting indicates a protocol error on the part of the caller
|
|
71
|
+
# It should crash the caller, but the exception isn't reraised
|
|
72
|
+
# Otherwise, it's a bug in this actor and should be reraised
|
|
73
|
+
raise unless ex.is_a?(AbortError)
|
|
74
|
+
ensure
|
|
75
|
+
Thread.current[:celluloid_chain_id] = nil
|
|
74
76
|
end
|
|
75
77
|
|
|
76
78
|
def cleanup
|
|
@@ -88,19 +90,17 @@ module Celluloid
|
|
|
88
90
|
|
|
89
91
|
# Asynchronous calls don't wait for a response
|
|
90
92
|
class AsyncCall < Call
|
|
91
|
-
def dispatch(obj)
|
|
92
|
-
begin
|
|
93
|
-
check_signature(obj)
|
|
94
|
-
rescue Exception => ex
|
|
95
|
-
Logger.crash("#{obj.class}: async call `#{@method}' failed!", ex)
|
|
96
|
-
return
|
|
97
|
-
end
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
def dispatch(obj)
|
|
95
|
+
Thread.current[:celluloid_chain_id] = Celluloid.uuid
|
|
96
|
+
super(obj)
|
|
100
97
|
rescue AbortError => ex
|
|
101
98
|
# Swallow aborted async calls, as they indicate the caller made a mistake
|
|
102
|
-
Logger.
|
|
99
|
+
Logger.debug("#{obj.class}: async call `#@method` aborted!\n#{Logger.format_exception(ex.cause)}")
|
|
100
|
+
ensure
|
|
101
|
+
Thread.current[:celluloid_chain_id] = nil
|
|
103
102
|
end
|
|
103
|
+
|
|
104
104
|
end
|
|
105
|
-
end
|
|
106
105
|
|
|
106
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Celluloid
|
|
2
|
+
class ConditionError < StandardError; end
|
|
3
|
+
|
|
4
|
+
# ConditionVariable-like signaling between tasks and actors
|
|
5
|
+
class Condition
|
|
6
|
+
attr_reader :owner
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@mutex = Mutex.new
|
|
10
|
+
@owner = Thread.current[:celluloid_actor]
|
|
11
|
+
@tasks = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Wait for the given signal and return the associated value
|
|
15
|
+
def wait
|
|
16
|
+
raise ConditionError, "cannot wait for signals while exclusive" if Celluloid.exclusive?
|
|
17
|
+
|
|
18
|
+
@mutex.synchronize do
|
|
19
|
+
actor = Thread.current[:celluloid_actor]
|
|
20
|
+
raise ConditionError, "can't wait for conditions outside actors" unless actor
|
|
21
|
+
raise ConditionError, "can't wait unless owner" unless actor == @owner
|
|
22
|
+
@tasks << Task.current
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
result = Task.suspend :condwait
|
|
26
|
+
raise result if result.is_a? ConditionError
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Send a signal to the first task waiting on this condition
|
|
31
|
+
def signal(value = nil)
|
|
32
|
+
@mutex.synchronize do
|
|
33
|
+
raise ConditionError, "no owner for this condition" unless @owner
|
|
34
|
+
|
|
35
|
+
if task = @tasks.shift
|
|
36
|
+
@owner.mailbox << SignalConditionRequest.new(task, value)
|
|
37
|
+
else
|
|
38
|
+
Logger.debug("Celluloid::Condition signaled spuriously")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Broadcast a value to all waiting tasks
|
|
44
|
+
def broadcast(value = nil)
|
|
45
|
+
@mutex.synchronize do
|
|
46
|
+
raise ConditionError, "no owner for this condition" unless @owner
|
|
47
|
+
|
|
48
|
+
@tasks.each { |task| @owner.mailbox << SignalConditionRequest.new(task, value) }
|
|
49
|
+
@tasks.clear
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Change the owner of this condition
|
|
54
|
+
def owner=(actor)
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
if @owner != actor
|
|
57
|
+
@tasks.each do |task|
|
|
58
|
+
ex = ConditionError.new("ownership changed")
|
|
59
|
+
@owner.mailbox << SignalConditionRequest.new(task, ex)
|
|
60
|
+
end
|
|
61
|
+
@tasks.clear
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@owner = actor
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
alias_method :inspect, :to_s
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/celluloid/core_ext.rb
CHANGED
|
@@ -6,7 +6,7 @@ class Thread
|
|
|
6
6
|
|
|
7
7
|
# Retrieve the mailbox for the current thread or lazily initialize it
|
|
8
8
|
def self.mailbox
|
|
9
|
-
current[:
|
|
9
|
+
current[:celluloid_mailbox] ||= Celluloid::Mailbox.new
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# Receive a message either as an actor or through the local mailbox
|
|
@@ -6,7 +6,11 @@ module Celluloid
|
|
|
6
6
|
when 'darwin'
|
|
7
7
|
@cores = Integer(`sysctl hw.ncpu`[/\d+/])
|
|
8
8
|
when 'linux'
|
|
9
|
-
@cores = File.
|
|
9
|
+
@cores = if File.exists?("/sys/devices/system/cpu/present")
|
|
10
|
+
File.read("/sys/devices/system/cpu/present").split('-').last.to_i+1
|
|
11
|
+
else
|
|
12
|
+
Dir["/sys/devices/system/cpu/cpu*"].select { |n| n=~/cpu\d+/ }.count
|
|
13
|
+
end
|
|
10
14
|
when 'mingw', 'mswin'
|
|
11
15
|
@cores = Integer(`SET NUMBER_OF_PROCESSORS`[/\d+/])
|
|
12
16
|
else
|
|
@@ -16,3 +20,5 @@ module Celluloid
|
|
|
16
20
|
def self.cores; @cores; end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
23
|
+
|
|
24
|
+
|