celluloid 0.11.1 → 0.12.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +23 -6
- data/lib/celluloid.rb +118 -109
- data/lib/celluloid/actor.rb +145 -55
- data/lib/celluloid/actor_proxy.rb +24 -21
- data/lib/celluloid/boot.rb +9 -0
- data/lib/celluloid/calls.rb +3 -3
- data/lib/celluloid/core_ext.rb +1 -6
- data/lib/celluloid/cpu_counter.rb +2 -0
- data/lib/celluloid/future.rb +3 -0
- data/lib/celluloid/links.rb +1 -3
- data/lib/celluloid/mailbox.rb +14 -20
- data/lib/celluloid/method.rb +19 -0
- data/lib/celluloid/notifications.rb +16 -17
- data/lib/celluloid/pool_manager.rb +40 -11
- data/lib/celluloid/registry.rb +8 -5
- data/lib/celluloid/rspec.rb +4 -0
- data/lib/celluloid/supervision_group.rb +23 -10
- data/lib/celluloid/system_events.rb +54 -0
- data/lib/celluloid/task.rb +7 -63
- data/lib/celluloid/tasks/task_fiber.rb +65 -0
- data/lib/celluloid/tasks/task_thread.rb +70 -0
- data/lib/celluloid/thread_handle.rb +2 -1
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +124 -8
- data/spec/support/example_actor_class.rb +1 -1
- data/spec/support/mailbox_examples.rb +3 -18
- data/spec/support/task_examples.rb +36 -0
- metadata +17 -12
- data/lib/celluloid/events.rb +0 -26
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
![Celluloid](https://github.com/celluloid/celluloid/
|
1
|
+
![Celluloid](https://raw.github.com/celluloid/celluloid-logos/master/celluloid/celluloid.png)
|
2
2
|
=========
|
3
3
|
[![Build Status](https://secure.travis-ci.org/celluloid/celluloid.png?branch=master)](http://travis-ci.org/celluloid/celluloid)
|
4
4
|
[![Dependency Status](https://gemnasium.com/celluloid/celluloid.png)](https://gemnasium.com/celluloid/celluloid)
|
@@ -17,7 +17,7 @@ Much of the difficulty with building concurrent programs in Ruby arises because
|
|
17
17
|
the object-oriented mechanisms for structuring code, such as classes and
|
18
18
|
inheritance, are separate from the concurrency mechanisms, such as threads and
|
19
19
|
locks. Celluloid combines these into a single structure, an active object
|
20
|
-
running within a thread, called an "actor".
|
20
|
+
running within a thread, called an "actor", or in Celluloid vernacular, a "cell".
|
21
21
|
|
22
22
|
By combining concurrency with object oriented programming, Celluloid frees you
|
23
23
|
up from worry about where to use threads and locks. Celluloid combines them
|
@@ -72,15 +72,32 @@ for more detailed documentation and usage notes.
|
|
72
72
|
Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
73
73
|
or visit us on IRC at #celluloid on freenode
|
74
74
|
|
75
|
-
### Is it any good?
|
76
|
-
|
77
|
-
[Yes.](http://news.ycombinator.com/item?id=3067434)
|
78
|
-
|
79
75
|
### Is It "Production Ready™"?
|
80
76
|
|
81
77
|
Yes, many users are now running Celluloid in production by using
|
82
78
|
[Sidekiq](https://github.com/mperham/sidekiq)
|
83
79
|
|
80
|
+
Installation
|
81
|
+
------------
|
82
|
+
|
83
|
+
Add this line to your application's Gemfile:
|
84
|
+
|
85
|
+
gem 'celluloid'
|
86
|
+
|
87
|
+
And then execute:
|
88
|
+
|
89
|
+
$ bundle
|
90
|
+
|
91
|
+
Or install it yourself as:
|
92
|
+
|
93
|
+
$ gem install celluloid
|
94
|
+
|
95
|
+
Inside of your Ruby program do:
|
96
|
+
|
97
|
+
require 'celluloid'
|
98
|
+
|
99
|
+
...to pull it in as a dependency.
|
100
|
+
|
84
101
|
Supported Platforms
|
85
102
|
-------------------
|
86
103
|
|
data/lib/celluloid.rb
CHANGED
@@ -4,14 +4,17 @@ require 'timeout'
|
|
4
4
|
require 'set'
|
5
5
|
|
6
6
|
module Celluloid
|
7
|
+
extend self # expose all instance methods as singleton methods
|
8
|
+
|
7
9
|
SHUTDOWN_TIMEOUT = 120 # How long actors have to terminate
|
8
|
-
@logger = Logger.new STDERR
|
9
10
|
|
10
11
|
class << self
|
11
|
-
attr_accessor :logger
|
12
|
+
attr_accessor :logger # Thread-safe logger class
|
13
|
+
attr_accessor :task_class # Default task type to use
|
12
14
|
|
13
15
|
def included(klass)
|
14
|
-
klass.send :extend,
|
16
|
+
klass.send :extend, ClassMethods
|
17
|
+
klass.send :include, InstanceMethods
|
15
18
|
end
|
16
19
|
|
17
20
|
# Are we currently inside of an actor?
|
@@ -19,36 +22,6 @@ module Celluloid
|
|
19
22
|
!!Thread.current[:actor]
|
20
23
|
end
|
21
24
|
|
22
|
-
# Is current actor running in exclusive mode?
|
23
|
-
def exclusive?
|
24
|
-
actor? and Thread.current[:actor].exclusive?
|
25
|
-
end
|
26
|
-
|
27
|
-
# Obtain the currently running actor (if one exists)
|
28
|
-
def current_actor
|
29
|
-
Actor.current
|
30
|
-
end
|
31
|
-
|
32
|
-
# Receive an asynchronous message
|
33
|
-
def receive(timeout = nil, &block)
|
34
|
-
actor = Thread.current[:actor]
|
35
|
-
if actor
|
36
|
-
actor.receive(timeout, &block)
|
37
|
-
else
|
38
|
-
Thread.mailbox.receive(timeout, &block)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Sleep letting the actor continue processing messages
|
43
|
-
def sleep(interval)
|
44
|
-
actor = Thread.current[:actor]
|
45
|
-
if actor
|
46
|
-
actor.sleep(interval)
|
47
|
-
else
|
48
|
-
Kernel.sleep interval
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
25
|
# Generate a Universally Unique Identifier
|
53
26
|
def uuid
|
54
27
|
UUID.generate
|
@@ -67,8 +40,6 @@ module Celluloid
|
|
67
40
|
end
|
68
41
|
|
69
42
|
# Shut down all running actors
|
70
|
-
# FIXME: This should probably attempt a graceful shutdown of the supervision
|
71
|
-
# tree before iterating through all actors and telling them to terminate.
|
72
43
|
def shutdown
|
73
44
|
Timeout.timeout(SHUTDOWN_TIMEOUT) do
|
74
45
|
actors = Actor.all
|
@@ -78,16 +49,16 @@ module Celluloid
|
|
78
49
|
Supervisor.root.terminate if Supervisor.root
|
79
50
|
|
80
51
|
# Actors cannot self-terminate, you must do it for them
|
81
|
-
|
52
|
+
Actor.all.each do |actor|
|
82
53
|
begin
|
83
|
-
actor.
|
54
|
+
actor.terminate!
|
84
55
|
rescue DeadActorError, MailboxError
|
85
56
|
end
|
86
57
|
end
|
87
58
|
|
88
|
-
|
59
|
+
Actor.all.each do |actor|
|
89
60
|
begin
|
90
|
-
|
61
|
+
Actor.join(actor)
|
91
62
|
rescue DeadActorError, MailboxError
|
92
63
|
end
|
93
64
|
end
|
@@ -104,7 +75,7 @@ module Celluloid
|
|
104
75
|
module ClassMethods
|
105
76
|
# Create a new actor
|
106
77
|
def new(*args, &block)
|
107
|
-
proxy = Actor.new(allocate).proxy
|
78
|
+
proxy = Actor.new(allocate, actor_options).proxy
|
108
79
|
proxy._send_(:initialize, *args, &block)
|
109
80
|
proxy
|
110
81
|
end
|
@@ -112,11 +83,10 @@ module Celluloid
|
|
112
83
|
|
113
84
|
# Create a new actor and link to the current one
|
114
85
|
def new_link(*args, &block)
|
115
|
-
|
116
|
-
raise NotActorError, "can't link outside actor context" unless current_actor
|
86
|
+
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
117
87
|
|
118
|
-
proxy = Actor.new(allocate).proxy
|
119
|
-
|
88
|
+
proxy = Actor.new(allocate, actor_options).proxy
|
89
|
+
Actor.link(proxy)
|
120
90
|
proxy._send_(:initialize, *args, &block)
|
121
91
|
proxy
|
122
92
|
end
|
@@ -158,9 +128,6 @@ module Celluloid
|
|
158
128
|
@exit_handler = callback.to_sym
|
159
129
|
end
|
160
130
|
|
161
|
-
# Obtain the exit handler for this actor
|
162
|
-
attr_reader :exit_handler
|
163
|
-
|
164
131
|
# Configure a custom mailbox factory
|
165
132
|
def use_mailbox(klass = nil, &block)
|
166
133
|
if block
|
@@ -170,31 +137,94 @@ module Celluloid
|
|
170
137
|
end
|
171
138
|
end
|
172
139
|
|
140
|
+
# Define the default task type for this class
|
141
|
+
def task_class(klass)
|
142
|
+
@task_class = klass
|
143
|
+
end
|
144
|
+
|
173
145
|
# Mark methods as running exclusively
|
174
146
|
def exclusive(*methods)
|
175
|
-
|
176
|
-
|
147
|
+
if methods.empty?
|
148
|
+
@exclusive_methods = :all
|
149
|
+
elsif @exclusive_methods != :all
|
150
|
+
@exclusive_methods ||= Set.new
|
151
|
+
@exclusive_methods.merge methods.map(&:to_sym)
|
152
|
+
end
|
177
153
|
end
|
178
|
-
attr_reader :exclusive_methods
|
179
154
|
|
180
155
|
# Create a mailbox for this actor
|
181
156
|
def mailbox_factory
|
182
157
|
if defined?(@mailbox_factory)
|
183
158
|
@mailbox_factory.call
|
184
|
-
elsif
|
185
|
-
|
159
|
+
elsif superclass.respond_to? :mailbox_factory
|
160
|
+
superclass.mailbox_factory
|
186
161
|
else
|
187
162
|
Mailbox.new
|
188
163
|
end
|
189
164
|
end
|
190
165
|
|
166
|
+
# Configuration options for Actor#new
|
167
|
+
def actor_options
|
168
|
+
{
|
169
|
+
:mailbox => mailbox_factory,
|
170
|
+
:exit_handler => @exit_handler,
|
171
|
+
:exclusive_methods => @exclusive_methods,
|
172
|
+
:task_class => @task_class,
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
191
176
|
def ===(other)
|
192
177
|
other.kind_of? self
|
193
178
|
end
|
194
179
|
end
|
195
180
|
|
181
|
+
# These are methods we don't want added to the Celluloid singleton but to be
|
182
|
+
# defined on all classes that use Celluloid
|
183
|
+
module InstanceMethods
|
184
|
+
# Obtain the Ruby object the actor is wrapping. This should ONLY be used
|
185
|
+
# for a limited set of use cases like runtime metaprogramming. Interacting
|
186
|
+
# directly with the wrapped object foregoes any kind of thread safety that
|
187
|
+
# Celluloid would ordinarily provide you, and the object is guaranteed to
|
188
|
+
# be shared with at least the actor thread. Tread carefully.
|
189
|
+
def wrapped_object; self; end
|
190
|
+
|
191
|
+
def inspect
|
192
|
+
str = "#<Celluloid::Actor(#{self.class}:0x#{object_id.to_s(16)})"
|
193
|
+
ivars = instance_variables.map do |ivar|
|
194
|
+
"#{ivar}=#{instance_variable_get(ivar).inspect}"
|
195
|
+
end
|
196
|
+
|
197
|
+
str << " " << ivars.join(' ') unless ivars.empty?
|
198
|
+
str << ">"
|
199
|
+
end
|
200
|
+
|
201
|
+
# Process async calls via method_missing
|
202
|
+
def method_missing(meth, *args, &block)
|
203
|
+
# bang methods are async calls
|
204
|
+
if meth.to_s.match(/!$/)
|
205
|
+
unbanged_meth = meth.to_s.sub(/!$/, '')
|
206
|
+
args.unshift unbanged_meth
|
207
|
+
|
208
|
+
call = AsyncCall.new(:__send__, args, block)
|
209
|
+
begin
|
210
|
+
Thread.current[:actor].mailbox << call
|
211
|
+
rescue MailboxError
|
212
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
213
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
214
|
+
# async calls should find other ways to deal with actors dying
|
215
|
+
# during an async call (i.e. linking/supervisors)
|
216
|
+
end
|
217
|
+
|
218
|
+
return
|
219
|
+
end
|
220
|
+
|
221
|
+
super
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
196
225
|
#
|
197
|
-
#
|
226
|
+
# The following methods are available on both the Celluloid singleton and
|
227
|
+
# directly inside of all classes that include Celluloid
|
198
228
|
#
|
199
229
|
|
200
230
|
# Is this actor alive?
|
@@ -217,16 +247,6 @@ module Celluloid
|
|
217
247
|
Thread.current[:actor].terminate
|
218
248
|
end
|
219
249
|
|
220
|
-
def inspect
|
221
|
-
str = "#<Celluloid::Actor(#{self.class}:0x#{object_id.to_s(16)})"
|
222
|
-
ivars = instance_variables.map do |ivar|
|
223
|
-
"#{ivar}=#{instance_variable_get(ivar).inspect}"
|
224
|
-
end
|
225
|
-
|
226
|
-
str << " " << ivars.join(' ') unless ivars.empty?
|
227
|
-
str << ">"
|
228
|
-
end
|
229
|
-
|
230
250
|
# Send a signal with the given name to all waiting methods
|
231
251
|
def signal(name, value = nil)
|
232
252
|
Thread.current[:actor].signal name, value
|
@@ -252,51 +272,59 @@ module Celluloid
|
|
252
272
|
Thread.current[:actor].tasks.to_a
|
253
273
|
end
|
254
274
|
|
255
|
-
# Obtain the Ruby object the actor is wrapping. This should ONLY be used
|
256
|
-
# for a limited set of use cases like runtime metaprogramming. Interacting
|
257
|
-
# directly with the wrapped object foregoes any kind of thread safety that
|
258
|
-
# Celluloid would ordinarily provide you, and the object is guaranteed to
|
259
|
-
# be shared with at least the actor thread. Tread carefully.
|
260
|
-
def wrapped_object; self; end
|
261
|
-
|
262
275
|
# Obtain the Celluloid::Links for this actor
|
263
276
|
def links
|
264
277
|
Thread.current[:actor].links
|
265
278
|
end
|
266
279
|
|
280
|
+
# Watch for exit events from another actor
|
281
|
+
def monitor(actor)
|
282
|
+
Actor.monitor(actor)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Stop waiting for exit events from another actor
|
286
|
+
def unmonitor(actor)
|
287
|
+
Actor.unmonitor(actor)
|
288
|
+
end
|
289
|
+
|
267
290
|
# Link this actor to another, allowing it to crash or react to errors
|
268
291
|
def link(actor)
|
269
|
-
|
270
|
-
notify_link actor
|
292
|
+
Actor.link(actor)
|
271
293
|
end
|
272
294
|
|
273
295
|
# Remove links to another actor
|
274
296
|
def unlink(actor)
|
275
|
-
|
276
|
-
notify_unlink actor
|
277
|
-
end
|
278
|
-
|
279
|
-
def notify_link(actor)
|
280
|
-
links << actor
|
297
|
+
Actor.unlink(actor)
|
281
298
|
end
|
282
299
|
|
283
|
-
|
284
|
-
|
300
|
+
# Are we monitoring another actor?
|
301
|
+
def monitoring?(actor)
|
302
|
+
Actor.monitoring?(actor)
|
285
303
|
end
|
286
304
|
|
287
305
|
# Is this actor linked to another?
|
288
306
|
def linked_to?(actor)
|
289
|
-
|
307
|
+
Actor.linked_to?(actor)
|
290
308
|
end
|
291
309
|
|
292
310
|
# Receive an asynchronous message via the actor protocol
|
293
311
|
def receive(timeout = nil, &block)
|
294
|
-
|
312
|
+
actor = Thread.current[:actor]
|
313
|
+
if actor
|
314
|
+
actor.receive(timeout, &block)
|
315
|
+
else
|
316
|
+
Thread.mailbox.receive(timeout, &block)
|
317
|
+
end
|
295
318
|
end
|
296
319
|
|
297
|
-
# Sleep
|
320
|
+
# Sleep letting the actor continue processing messages
|
298
321
|
def sleep(interval)
|
299
|
-
|
322
|
+
actor = Thread.current[:actor]
|
323
|
+
if actor
|
324
|
+
actor.sleep(interval)
|
325
|
+
else
|
326
|
+
Kernel.sleep interval
|
327
|
+
end
|
300
328
|
end
|
301
329
|
|
302
330
|
# Run given block in an exclusive mode: all synchronous calls block the whole
|
@@ -307,7 +335,8 @@ module Celluloid
|
|
307
335
|
|
308
336
|
# Are we currently exclusive
|
309
337
|
def exclusive?
|
310
|
-
|
338
|
+
actor = Thread.current[:actor]
|
339
|
+
actor && actor.exclusive?
|
311
340
|
end
|
312
341
|
|
313
342
|
# Call a block after a given interval, returning a Celluloid::Timer object
|
@@ -338,29 +367,6 @@ module Celluloid
|
|
338
367
|
def future(meth, *args, &block)
|
339
368
|
Actor.future Thread.current[:actor].mailbox, meth, *args, &block
|
340
369
|
end
|
341
|
-
|
342
|
-
# Process async calls via method_missing
|
343
|
-
def method_missing(meth, *args, &block)
|
344
|
-
# bang methods are async calls
|
345
|
-
if meth.to_s.match(/!$/)
|
346
|
-
unbanged_meth = meth.to_s.sub(/!$/, '')
|
347
|
-
args.unshift unbanged_meth
|
348
|
-
|
349
|
-
call = AsyncCall.new(:__send__, args, block)
|
350
|
-
begin
|
351
|
-
Thread.current[:actor].mailbox << call
|
352
|
-
rescue MailboxError
|
353
|
-
# Silently swallow asynchronous calls to dead actors. There's no way
|
354
|
-
# to reliably generate DeadActorErrors for async calls, so users of
|
355
|
-
# async calls should find other ways to deal with actors dying
|
356
|
-
# during an async call (i.e. linking/supervisors)
|
357
|
-
end
|
358
|
-
|
359
|
-
return
|
360
|
-
end
|
361
|
-
|
362
|
-
super
|
363
|
-
end
|
364
370
|
end
|
365
371
|
|
366
372
|
require 'celluloid/version'
|
@@ -368,17 +374,18 @@ require 'celluloid/actor_proxy'
|
|
368
374
|
require 'celluloid/calls'
|
369
375
|
require 'celluloid/core_ext'
|
370
376
|
require 'celluloid/cpu_counter'
|
371
|
-
require 'celluloid/events'
|
372
377
|
require 'celluloid/fiber'
|
373
378
|
require 'celluloid/fsm'
|
374
379
|
require 'celluloid/internal_pool'
|
375
380
|
require 'celluloid/links'
|
376
381
|
require 'celluloid/logger'
|
377
382
|
require 'celluloid/mailbox'
|
383
|
+
require 'celluloid/method'
|
378
384
|
require 'celluloid/receivers'
|
379
385
|
require 'celluloid/registry'
|
380
386
|
require 'celluloid/responses'
|
381
387
|
require 'celluloid/signals'
|
388
|
+
require 'celluloid/system_events'
|
382
389
|
require 'celluloid/task'
|
383
390
|
require 'celluloid/thread_handle'
|
384
391
|
require 'celluloid/uuid'
|
@@ -389,3 +396,5 @@ require 'celluloid/pool_manager'
|
|
389
396
|
require 'celluloid/supervision_group'
|
390
397
|
require 'celluloid/supervisor'
|
391
398
|
require 'celluloid/notifications'
|
399
|
+
|
400
|
+
require 'celluloid/boot'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -7,6 +7,9 @@ module Celluloid
|
|
7
7
|
# Trying to do something to a dead actor
|
8
8
|
class DeadActorError < StandardError; end
|
9
9
|
|
10
|
+
# A timeout occured before the given request could complete
|
11
|
+
class TimeoutError < StandardError; end
|
12
|
+
|
10
13
|
# The caller made an error, not the current actor
|
11
14
|
class AbortError < StandardError
|
12
15
|
attr_reader :cause
|
@@ -17,6 +20,8 @@ module Celluloid
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
LINKING_TIMEOUT = 5 # linking times out after 5 seconds
|
24
|
+
|
20
25
|
# Actors are Celluloid's concurrency primitive. They're implemented as
|
21
26
|
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
22
27
|
# messages.
|
@@ -64,9 +69,13 @@ module Celluloid
|
|
64
69
|
# The current task will be automatically resumed when we get a response
|
65
70
|
Task.suspend(:callwait).value
|
66
71
|
else
|
67
|
-
# Otherwise we're inside a normal thread, so block
|
68
|
-
response =
|
69
|
-
|
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)
|
70
79
|
end
|
71
80
|
|
72
81
|
response.value
|
@@ -97,18 +106,68 @@ module Celluloid
|
|
97
106
|
actors = []
|
98
107
|
Thread.list.each do |t|
|
99
108
|
actor = t[:actor]
|
100
|
-
actors << actor.proxy if actor
|
109
|
+
actors << actor.proxy if actor and actor.respond_to?(:proxy)
|
101
110
|
end
|
102
111
|
actors
|
103
112
|
end
|
113
|
+
|
114
|
+
# Watch for exit events from another actor
|
115
|
+
def monitor(actor)
|
116
|
+
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
117
|
+
Thread.current[:actor].linking_request(actor, :link)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Stop waiting for exit events from another actor
|
121
|
+
def unmonitor(actor)
|
122
|
+
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
123
|
+
Thread.current[:actor].linking_request(actor, :unlink)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Link to another actor
|
127
|
+
def link(actor)
|
128
|
+
monitor actor
|
129
|
+
Thread.current[:actor].links << actor
|
130
|
+
end
|
131
|
+
|
132
|
+
# Unlink from another actor
|
133
|
+
def unlink(actor)
|
134
|
+
unmonitor actor
|
135
|
+
Thread.current[:actor].links.delete actor
|
136
|
+
end
|
137
|
+
|
138
|
+
# Are we monitoring the given actor?
|
139
|
+
def monitoring?(actor)
|
140
|
+
actor.links.include? Actor.current
|
141
|
+
end
|
142
|
+
|
143
|
+
# Are we bidirectionally linked to the given actor?
|
144
|
+
def linked_to?(actor)
|
145
|
+
monitoring?(actor) && Thread.current[:actor].links.include?(actor)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Forcibly kill a given actor
|
149
|
+
def kill(actor)
|
150
|
+
actor.thread.kill
|
151
|
+
begin
|
152
|
+
actor.mailbox.shutdown
|
153
|
+
rescue DeadActorError
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Wait for an actor to terminate
|
158
|
+
def join(actor)
|
159
|
+
actor.thread.join
|
160
|
+
actor
|
161
|
+
end
|
104
162
|
end
|
105
163
|
|
106
164
|
# Wrap the given subject with an Actor
|
107
|
-
def initialize(subject)
|
165
|
+
def initialize(subject, options = {})
|
108
166
|
@subject = subject
|
109
|
-
@mailbox =
|
110
|
-
@exit_handler =
|
111
|
-
@exclusives =
|
167
|
+
@mailbox = options[:mailbox] || Mailbox.new
|
168
|
+
@exit_handler = options[:exit_handler]
|
169
|
+
@exclusives = options[:exclusive_methods]
|
170
|
+
@task_class = options[:task_class] || Celluloid.task_class
|
112
171
|
|
113
172
|
@tasks = Set.new
|
114
173
|
@links = Links.new
|
@@ -128,6 +187,33 @@ module Celluloid
|
|
128
187
|
@proxy = ActorProxy.new(self)
|
129
188
|
end
|
130
189
|
|
190
|
+
# Run the actor loop
|
191
|
+
def run
|
192
|
+
begin
|
193
|
+
while @running
|
194
|
+
if message = @mailbox.receive(timeout)
|
195
|
+
handle_message message
|
196
|
+
else
|
197
|
+
# No message indicates a timeout
|
198
|
+
@timers.fire
|
199
|
+
@receivers.fire_timers
|
200
|
+
end
|
201
|
+
end
|
202
|
+
rescue MailboxShutdown
|
203
|
+
# If the mailbox detects shutdown, exit the actor
|
204
|
+
end
|
205
|
+
|
206
|
+
shutdown
|
207
|
+
rescue Exception => ex
|
208
|
+
handle_crash(ex)
|
209
|
+
raise unless ex.is_a? StandardError
|
210
|
+
end
|
211
|
+
|
212
|
+
# Terminate this actor
|
213
|
+
def terminate
|
214
|
+
@running = false
|
215
|
+
end
|
216
|
+
|
131
217
|
# Is this actor running in exclusive mode?
|
132
218
|
def exclusive?
|
133
219
|
@exclusive
|
@@ -141,9 +227,33 @@ module Celluloid
|
|
141
227
|
@exclusive = false
|
142
228
|
end
|
143
229
|
|
144
|
-
#
|
145
|
-
def
|
146
|
-
|
230
|
+
# Perform a linking request with another actor
|
231
|
+
def linking_request(receiver, type)
|
232
|
+
exclusive do
|
233
|
+
start_time = Time.now
|
234
|
+
|
235
|
+
receiver.mailbox << LinkingRequest.new(Actor.current, type)
|
236
|
+
system_events = []
|
237
|
+
loop do
|
238
|
+
wait_interval = start_time + LINKING_TIMEOUT - Time.now
|
239
|
+
message = @mailbox.receive(wait_interval) do |msg|
|
240
|
+
msg.is_a?(LinkingResponse) && msg.actor == receiver && msg.type == type
|
241
|
+
end
|
242
|
+
|
243
|
+
case message
|
244
|
+
when LinkingResponse
|
245
|
+
# We're done!
|
246
|
+
system_events.each { |ev| handle_system_event(ev) }
|
247
|
+
return
|
248
|
+
when NilClass
|
249
|
+
raise TimeoutError, "linking timeout of #{LINKING_TIMEOUT} seconds exceeded"
|
250
|
+
when SystemEvent
|
251
|
+
# Queue up pending system events to be processed after we've successfully linked
|
252
|
+
system_events << message
|
253
|
+
else raise 'wtf'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
147
257
|
end
|
148
258
|
|
149
259
|
# Send a signal with the given name to all waiting methods
|
@@ -158,37 +268,12 @@ module Celluloid
|
|
158
268
|
|
159
269
|
# Receive an asynchronous message
|
160
270
|
def receive(timeout = nil, &block)
|
161
|
-
|
162
|
-
@receivers.receive(timeout, &block)
|
163
|
-
|
164
|
-
handle_system_event(event)
|
165
|
-
retry
|
166
|
-
end
|
167
|
-
end
|
271
|
+
loop do
|
272
|
+
message = @receivers.receive(timeout, &block)
|
273
|
+
break message unless message.is_a?(SystemEvent)
|
168
274
|
|
169
|
-
|
170
|
-
def run
|
171
|
-
begin
|
172
|
-
while @running
|
173
|
-
if message = @mailbox.receive(timeout)
|
174
|
-
handle_message message
|
175
|
-
else
|
176
|
-
# No message indicates a timeout
|
177
|
-
@timers.fire
|
178
|
-
@receivers.fire_timers
|
179
|
-
end
|
180
|
-
end
|
181
|
-
rescue SystemEvent => event
|
182
|
-
handle_system_event event
|
183
|
-
retry
|
184
|
-
rescue MailboxShutdown
|
185
|
-
# If the mailbox detects shutdown, exit the actor
|
275
|
+
handle_system_event(message)
|
186
276
|
end
|
187
|
-
|
188
|
-
shutdown
|
189
|
-
rescue Exception => ex
|
190
|
-
handle_crash(ex)
|
191
|
-
raise unless ex.is_a? StandardError
|
192
277
|
end
|
193
278
|
|
194
279
|
# How long to wait until the next timer fires
|
@@ -206,17 +291,13 @@ module Celluloid
|
|
206
291
|
end
|
207
292
|
|
208
293
|
# Schedule a block to run at the given time
|
209
|
-
def after(interval)
|
210
|
-
@timers.after(interval)
|
211
|
-
Task.new(:timer) { yield }.resume
|
212
|
-
end
|
294
|
+
def after(interval, &block)
|
295
|
+
@timers.after(interval) { task(:timer, &block) }
|
213
296
|
end
|
214
297
|
|
215
298
|
# Schedule a block to run at the given time
|
216
|
-
def every(interval)
|
217
|
-
@timers.every(interval)
|
218
|
-
Task.new(:timer) { yield }.resume
|
219
|
-
end
|
299
|
+
def every(interval, &block)
|
300
|
+
@timers.every(interval) { task(:timer, &block) }
|
220
301
|
end
|
221
302
|
|
222
303
|
# Sleep for the given amount of time
|
@@ -233,12 +314,10 @@ module Celluloid
|
|
233
314
|
# Handle standard low-priority messages
|
234
315
|
def handle_message(message)
|
235
316
|
case message
|
317
|
+
when SystemEvent
|
318
|
+
handle_system_event message
|
236
319
|
when Call
|
237
|
-
|
238
|
-
exclusive { message.dispatch(@subject) }
|
239
|
-
else
|
240
|
-
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
241
|
-
end
|
320
|
+
task(:message_handler, message.method) { message.dispatch(@subject) }
|
242
321
|
when Response
|
243
322
|
message.dispatch
|
244
323
|
else
|
@@ -251,7 +330,9 @@ module Celluloid
|
|
251
330
|
def handle_system_event(event)
|
252
331
|
case event
|
253
332
|
when ExitEvent
|
254
|
-
|
333
|
+
task(:exit_handler, @exit_handler) { handle_exit_event event }
|
334
|
+
when LinkingRequest
|
335
|
+
event.process(links)
|
255
336
|
when NamingRequest
|
256
337
|
@name = event.name
|
257
338
|
when TerminationRequest
|
@@ -289,7 +370,7 @@ module Celluloid
|
|
289
370
|
# Run the user-defined finalizer, if one is set
|
290
371
|
def run_finalizer
|
291
372
|
return unless @subject.respond_to? :finalize
|
292
|
-
|
373
|
+
task(:finalizer, :finalize) { @subject.finalize }
|
293
374
|
rescue => ex
|
294
375
|
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
295
376
|
end
|
@@ -302,5 +383,14 @@ module Celluloid
|
|
302
383
|
rescue => ex
|
303
384
|
Logger.crash("#{@subject.class}: CLEANUP CRASHED!", ex)
|
304
385
|
end
|
386
|
+
|
387
|
+
# Run a method inside a task unless it's exclusive
|
388
|
+
def task(task_type, method_name = nil, &block)
|
389
|
+
if @exclusives && (@exclusives == :all || @exclusives.include?(method_name))
|
390
|
+
exclusive { block.call }
|
391
|
+
else
|
392
|
+
@task_class.new(:message_handler, &block).resume
|
393
|
+
end
|
394
|
+
end
|
305
395
|
end
|
306
396
|
end
|