celluloid 0.11.1 → 0.12.0.pre
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 +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
|
-

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