celluloid 0.11.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/lib/celluloid.rb +15 -2
- data/lib/celluloid/actor.rb +56 -30
- data/lib/celluloid/actor_proxy.rb +2 -1
- data/lib/celluloid/calls.rb +6 -5
- data/lib/celluloid/notifications.rb +84 -0
- data/lib/celluloid/pool_manager.rb +30 -6
- data/lib/celluloid/receivers.rb +2 -1
- data/lib/celluloid/registry.rb +35 -9
- data/lib/celluloid/responses.rb +4 -0
- data/lib/celluloid/supervision_group.rb +71 -29
- data/lib/celluloid/supervisor.rb +5 -52
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +30 -87
- data/spec/support/example_actor_class.rb +79 -0
- data/spec/support/mailbox_examples.rb +4 -1
- metadata +17 -16
- data/lib/celluloid/timers.rb +0 -110
data/README.md
CHANGED
@@ -50,7 +50,7 @@ features which make concurrent programming simple, easy, and fun:
|
|
50
50
|
a clean state. Celluloid provides its own implementation of the core
|
51
51
|
fault-tolerance concepts in Erlang including [linking](https://github.com/celluloid/celluloid/wiki/Linking),
|
52
52
|
[supervisors](https://github.com/celluloid/celluloid/wiki/Supervisors),
|
53
|
-
and [supervision
|
53
|
+
and [supervision groups](https://github.com/celluloid/celluloid/wiki/Supervision-Groups).
|
54
54
|
|
55
55
|
* __[Futures](https://github.com/celluloid/celluloid/wiki/futures):__
|
56
56
|
Ever wanted to call a method "in the background" and retrieve the
|
data/lib/celluloid.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'thread'
|
3
3
|
require 'timeout'
|
4
|
+
require 'set'
|
4
5
|
|
5
6
|
module Celluloid
|
6
7
|
SHUTDOWN_TIMEOUT = 120 # How long actors have to terminate
|
@@ -169,6 +170,13 @@ module Celluloid
|
|
169
170
|
end
|
170
171
|
end
|
171
172
|
|
173
|
+
# Mark methods as running exclusively
|
174
|
+
def exclusive(*methods)
|
175
|
+
@exclusive_methods ||= Set.new
|
176
|
+
@exclusive_methods.merge methods.map(&:to_sym)
|
177
|
+
end
|
178
|
+
attr_reader :exclusive_methods
|
179
|
+
|
172
180
|
# Create a mailbox for this actor
|
173
181
|
def mailbox_factory
|
174
182
|
if defined?(@mailbox_factory)
|
@@ -297,6 +305,11 @@ module Celluloid
|
|
297
305
|
Thread.current[:actor].exclusive(&block)
|
298
306
|
end
|
299
307
|
|
308
|
+
# Are we currently exclusive
|
309
|
+
def exclusive?
|
310
|
+
Celluloid.exclusive?
|
311
|
+
end
|
312
|
+
|
300
313
|
# Call a block after a given interval, returning a Celluloid::Timer object
|
301
314
|
def after(interval, &block)
|
302
315
|
Thread.current[:actor].after(interval, &block)
|
@@ -333,7 +346,7 @@ module Celluloid
|
|
333
346
|
unbanged_meth = meth.to_s.sub(/!$/, '')
|
334
347
|
args.unshift unbanged_meth
|
335
348
|
|
336
|
-
call = AsyncCall.new(
|
349
|
+
call = AsyncCall.new(:__send__, args, block)
|
337
350
|
begin
|
338
351
|
Thread.current[:actor].mailbox << call
|
339
352
|
rescue MailboxError
|
@@ -368,7 +381,6 @@ require 'celluloid/responses'
|
|
368
381
|
require 'celluloid/signals'
|
369
382
|
require 'celluloid/task'
|
370
383
|
require 'celluloid/thread_handle'
|
371
|
-
require 'celluloid/timers'
|
372
384
|
require 'celluloid/uuid'
|
373
385
|
|
374
386
|
require 'celluloid/actor'
|
@@ -376,3 +388,4 @@ require 'celluloid/future'
|
|
376
388
|
require 'celluloid/pool_manager'
|
377
389
|
require 'celluloid/supervision_group'
|
378
390
|
require 'celluloid/supervisor'
|
391
|
+
require 'celluloid/notifications'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'timers'
|
2
|
+
|
1
3
|
module Celluloid
|
2
4
|
# Don't do Actor-like things outside Actor scope
|
3
5
|
class NotActorError < StandardError; end
|
@@ -19,10 +21,21 @@ module Celluloid
|
|
19
21
|
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
20
22
|
# messages.
|
21
23
|
class Actor
|
22
|
-
extend Registry
|
23
24
|
attr_reader :subject, :proxy, :tasks, :links, :mailbox, :thread, :name
|
24
25
|
|
25
26
|
class << self
|
27
|
+
extend Forwardable
|
28
|
+
|
29
|
+
def_delegators "Celluloid::Registry.root", :[], :[]=
|
30
|
+
|
31
|
+
def registered
|
32
|
+
Registry.root.names
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear_registry
|
36
|
+
Registry.root.clear
|
37
|
+
end
|
38
|
+
|
26
39
|
# Obtain the current actor
|
27
40
|
def current
|
28
41
|
actor = Thread.current[:actor]
|
@@ -63,7 +76,7 @@ module Celluloid
|
|
63
76
|
# Invoke a method asynchronously on an actor via its mailbox
|
64
77
|
def async(mailbox, meth, *args, &block)
|
65
78
|
begin
|
66
|
-
mailbox << AsyncCall.new(
|
79
|
+
mailbox << AsyncCall.new(meth, args, block)
|
67
80
|
rescue MailboxError
|
68
81
|
# Silently swallow asynchronous calls to dead actors. There's no way
|
69
82
|
# to reliably generate DeadActorErrors for async calls, so users of
|
@@ -92,8 +105,11 @@ module Celluloid
|
|
92
105
|
|
93
106
|
# Wrap the given subject with an Actor
|
94
107
|
def initialize(subject)
|
95
|
-
@subject
|
96
|
-
@mailbox
|
108
|
+
@subject = subject
|
109
|
+
@mailbox = subject.class.mailbox_factory
|
110
|
+
@exit_handler = subject.class.exit_handler
|
111
|
+
@exclusives = subject.class.exclusive_methods
|
112
|
+
|
97
113
|
@tasks = Set.new
|
98
114
|
@links = Links.new
|
99
115
|
@signals = Signals.new
|
@@ -142,26 +158,19 @@ module Celluloid
|
|
142
158
|
|
143
159
|
# Receive an asynchronous message
|
144
160
|
def receive(timeout = nil, &block)
|
145
|
-
|
161
|
+
begin
|
162
|
+
@receivers.receive(timeout, &block)
|
163
|
+
rescue SystemEvent => event
|
164
|
+
handle_system_event(event)
|
165
|
+
retry
|
166
|
+
end
|
146
167
|
end
|
147
168
|
|
148
169
|
# Run the actor loop
|
149
170
|
def run
|
150
171
|
begin
|
151
172
|
while @running
|
152
|
-
|
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
|
173
|
+
if message = @mailbox.receive(timeout)
|
165
174
|
handle_message message
|
166
175
|
else
|
167
176
|
# No message indicates a timeout
|
@@ -169,6 +178,9 @@ module Celluloid
|
|
169
178
|
@receivers.fire_timers
|
170
179
|
end
|
171
180
|
end
|
181
|
+
rescue SystemEvent => event
|
182
|
+
handle_system_event event
|
183
|
+
retry
|
172
184
|
rescue MailboxShutdown
|
173
185
|
# If the mailbox detects shutdown, exit the actor
|
174
186
|
end
|
@@ -195,14 +207,14 @@ module Celluloid
|
|
195
207
|
|
196
208
|
# Schedule a block to run at the given time
|
197
209
|
def after(interval)
|
198
|
-
@timers.
|
210
|
+
@timers.after(interval) do
|
199
211
|
Task.new(:timer) { yield }.resume
|
200
212
|
end
|
201
213
|
end
|
202
214
|
|
203
215
|
# Schedule a block to run at the given time
|
204
216
|
def every(interval)
|
205
|
-
@timers.
|
217
|
+
@timers.every(interval) do
|
206
218
|
Task.new(:timer) { yield }.resume
|
207
219
|
end
|
208
220
|
end
|
@@ -213,34 +225,48 @@ module Celluloid
|
|
213
225
|
Kernel.sleep(interval)
|
214
226
|
else
|
215
227
|
task = Task.current
|
216
|
-
@timers.
|
228
|
+
@timers.after(interval) { task.resume }
|
217
229
|
Task.suspend :sleeping
|
218
230
|
end
|
219
231
|
end
|
220
232
|
|
221
|
-
# Handle
|
233
|
+
# Handle standard low-priority messages
|
222
234
|
def handle_message(message)
|
223
235
|
case message
|
224
236
|
when Call
|
225
|
-
|
237
|
+
if @exclusives && @exclusives.include?(message.method)
|
238
|
+
exclusive { message.dispatch(@subject) }
|
239
|
+
else
|
240
|
+
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
241
|
+
end
|
226
242
|
when Response
|
227
|
-
message.
|
243
|
+
message.dispatch
|
228
244
|
else
|
229
245
|
@receivers.handle_message(message)
|
230
246
|
end
|
231
247
|
message
|
232
248
|
end
|
233
249
|
|
234
|
-
# Handle
|
235
|
-
def
|
236
|
-
|
237
|
-
|
238
|
-
|
250
|
+
# Handle high-priority system event messages
|
251
|
+
def handle_system_event(event)
|
252
|
+
case event
|
253
|
+
when ExitEvent
|
254
|
+
Task.new(:exit_handler) { handle_exit_event event }.resume
|
255
|
+
when NamingRequest
|
256
|
+
@name = event.name
|
257
|
+
when TerminationRequest
|
258
|
+
@running = false
|
239
259
|
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Handle exit events received by this actor
|
263
|
+
def handle_exit_event(event)
|
264
|
+
# Run the exit handler if available
|
265
|
+
return @subject.send(@exit_handler, event.actor, event.reason) if @exit_handler
|
240
266
|
|
241
267
|
# Reraise exceptions from linked actors
|
242
268
|
# If no reason is given, actor terminated cleanly
|
243
|
-
raise
|
269
|
+
raise event.reason if event.reason
|
244
270
|
end
|
245
271
|
|
246
272
|
# Handle any exceptions that occur within a running actor
|
data/lib/celluloid/calls.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Calls represent requests to an actor
|
3
3
|
class Call
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :method, :arguments, :block
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(method, arguments = [], block = nil)
|
7
|
+
@method, @arguments, @block = method, arguments, block
|
8
8
|
end
|
9
9
|
|
10
10
|
def check_signature(obj)
|
@@ -37,10 +37,11 @@ module Celluloid
|
|
37
37
|
|
38
38
|
# Synchronous calls wait for a response
|
39
39
|
class SyncCall < Call
|
40
|
-
attr_reader :task
|
40
|
+
attr_reader :caller, :task
|
41
41
|
|
42
42
|
def initialize(caller, method, arguments = [], block = nil, task = Fiber.current.task)
|
43
|
-
super(
|
43
|
+
super(method, arguments, block)
|
44
|
+
@caller = caller
|
44
45
|
@task = task
|
45
46
|
end
|
46
47
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module Notifications
|
3
|
+
class Fanout
|
4
|
+
include Celluloid
|
5
|
+
trap_exit :prune
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@subscribers = []
|
9
|
+
@listeners_for = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def subscribe(actor, pattern, method)
|
13
|
+
subscriber = Subscriber.new(actor, pattern, method).tap do |s|
|
14
|
+
@subscribers << s
|
15
|
+
end
|
16
|
+
link actor
|
17
|
+
@listeners_for.clear
|
18
|
+
subscriber
|
19
|
+
end
|
20
|
+
|
21
|
+
def unsubscribe(subscriber)
|
22
|
+
@subscribers.reject! { |s| s.matches?(subscriber) }
|
23
|
+
@listeners_for.clear
|
24
|
+
end
|
25
|
+
|
26
|
+
def publish(pattern, *args)
|
27
|
+
listeners_for(pattern).each { |s| s.publish(pattern, *args) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def listeners_for(pattern)
|
31
|
+
@listeners_for[pattern] ||= @subscribers.select { |s| s.subscribed_to?(pattern) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def listening?(pattern)
|
35
|
+
listeners_for(pattern).any?
|
36
|
+
end
|
37
|
+
|
38
|
+
def prune(actor, reason=nil)
|
39
|
+
@subscribers.reject! { |s| s.actor == actor }
|
40
|
+
@listeners_for.clear
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Subscriber
|
45
|
+
attr_accessor :actor, :pattern, :method
|
46
|
+
|
47
|
+
def initialize(actor, pattern, method)
|
48
|
+
@actor = actor
|
49
|
+
@pattern = pattern
|
50
|
+
@method = method
|
51
|
+
end
|
52
|
+
|
53
|
+
def publish(pattern, *args)
|
54
|
+
Actor.async(actor.mailbox, method, pattern, *args)
|
55
|
+
end
|
56
|
+
|
57
|
+
def subscribed_to?(pattern)
|
58
|
+
!pattern || @pattern === pattern.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def matches?(subscriber_or_pattern)
|
62
|
+
self === subscriber_or_pattern ||
|
63
|
+
@pattern && @pattern === subscriber_or_pattern
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class << self
|
68
|
+
attr_accessor :notifier
|
69
|
+
end
|
70
|
+
self.notifier = Fanout.new
|
71
|
+
|
72
|
+
def publish(pattern, *args)
|
73
|
+
Celluloid::Notifications.notifier.publish(pattern, *args)
|
74
|
+
end
|
75
|
+
|
76
|
+
def subscribe(pattern, method)
|
77
|
+
Celluloid::Notifications.notifier.subscribe(Actor.current, pattern, method)
|
78
|
+
end
|
79
|
+
|
80
|
+
def unsubscribe(*args)
|
81
|
+
Celluloid::Notifications.notifier.unsubscribe(*args)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Manages a fixed-size pool of workers
|
3
3
|
# Delegates work (i.e. methods) and supervises workers
|
4
|
+
# Don't use this class directly. Instead use MyKlass.pool
|
4
5
|
class PoolManager
|
5
6
|
include Celluloid
|
6
7
|
trap_exit :crash_handler
|
@@ -16,21 +17,44 @@ module Celluloid
|
|
16
17
|
@idle = @size.times.map { worker_class.new_link(*@args) }
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
worker = provision_worker
|
20
|
+
def _send_(method, *args, &block)
|
21
|
+
worker = __provision_worker
|
22
22
|
|
23
23
|
begin
|
24
24
|
worker._send_ method, *args, &block
|
25
|
-
rescue => ex
|
25
|
+
rescue Exception => ex
|
26
26
|
abort ex
|
27
27
|
ensure
|
28
28
|
@idle << worker if worker.alive?
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
def name
|
33
|
+
_send_ @mailbox, :name
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_a?(klass)
|
37
|
+
_send_ :is_a?, klass
|
38
|
+
end
|
39
|
+
|
40
|
+
def kind_of?(klass)
|
41
|
+
_send_ :kind_of?, klass
|
42
|
+
end
|
43
|
+
|
44
|
+
def methods(include_ancestors = true)
|
45
|
+
_send_ :methods, include_ancestors
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
_send_ :to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
_send_ :inspect
|
54
|
+
end
|
55
|
+
|
32
56
|
# Provision a new worker
|
33
|
-
def
|
57
|
+
def __provision_worker
|
34
58
|
while @idle.empty?
|
35
59
|
# Using exclusive mode blocks incoming messages, so they don't pile
|
36
60
|
# up as waiting Celluloid::Tasks
|
@@ -53,7 +77,7 @@ module Celluloid
|
|
53
77
|
|
54
78
|
def method_missing(method, *args, &block)
|
55
79
|
if respond_to?(method)
|
56
|
-
|
80
|
+
_send_ method, *args, &block
|
57
81
|
else
|
58
82
|
super
|
59
83
|
end
|
data/lib/celluloid/receivers.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'timers'
|
2
3
|
|
3
4
|
module Celluloid
|
4
5
|
# Allow methods to directly interact with the actor protocol
|
@@ -16,7 +17,7 @@ module Celluloid
|
|
16
17
|
receiver = Receiver.new block
|
17
18
|
|
18
19
|
if timeout
|
19
|
-
receiver.timer = @timers.
|
20
|
+
receiver.timer = @timers.after(timeout) do
|
20
21
|
@receivers.delete receiver
|
21
22
|
receiver.resume
|
22
23
|
end
|
data/lib/celluloid/registry.rb
CHANGED
@@ -2,9 +2,15 @@ require 'thread'
|
|
2
2
|
|
3
3
|
module Celluloid
|
4
4
|
# The Registry allows us to refer to specific actors by human-meaningful names
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class Registry
|
6
|
+
def self.root
|
7
|
+
@root ||= new
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@registry = {}
|
12
|
+
@registry_lock = Mutex.new
|
13
|
+
end
|
8
14
|
|
9
15
|
# Register an Actor
|
10
16
|
def []=(name, actor)
|
@@ -13,8 +19,8 @@ module Celluloid
|
|
13
19
|
raise TypeError, "not an actor"
|
14
20
|
end
|
15
21
|
|
16
|
-
|
17
|
-
|
22
|
+
@registry_lock.synchronize do
|
23
|
+
@registry[name.to_sym] = actor
|
18
24
|
end
|
19
25
|
|
20
26
|
actor.mailbox.system_event NamingRequest.new(name.to_sym)
|
@@ -22,14 +28,34 @@ module Celluloid
|
|
22
28
|
|
23
29
|
# Retrieve an actor by name
|
24
30
|
def [](name)
|
25
|
-
|
26
|
-
|
31
|
+
@registry_lock.synchronize do
|
32
|
+
@registry[name.to_sym]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :get, :[]
|
37
|
+
alias_method :set, :[]=
|
38
|
+
|
39
|
+
def delete(name)
|
40
|
+
@registry_lock.synchronize do
|
41
|
+
@registry[name.to_sym]
|
27
42
|
end
|
28
43
|
end
|
29
44
|
|
30
45
|
# List all registered actors by name
|
31
|
-
def
|
32
|
-
|
46
|
+
def names
|
47
|
+
@registry_lock.synchronize { @registry.keys }
|
48
|
+
end
|
49
|
+
|
50
|
+
# removes and returns all registered actors as a hash of `name => actor`
|
51
|
+
# can be used in testing to clear the registry
|
52
|
+
def clear
|
53
|
+
hash = nil
|
54
|
+
@registry_lock.synchronize do
|
55
|
+
hash = @registry.dup
|
56
|
+
@registry.clear
|
57
|
+
end
|
58
|
+
hash
|
33
59
|
end
|
34
60
|
end
|
35
61
|
end
|
data/lib/celluloid/responses.rb
CHANGED
@@ -6,12 +6,19 @@ module Celluloid
|
|
6
6
|
|
7
7
|
class << self
|
8
8
|
# Actors or sub-applications to be supervised
|
9
|
-
def
|
10
|
-
@
|
9
|
+
def blocks
|
10
|
+
@blocks ||= []
|
11
11
|
end
|
12
12
|
|
13
13
|
# Start this application (and watch it with a supervisor)
|
14
|
-
|
14
|
+
def run!
|
15
|
+
group = new do |group|
|
16
|
+
blocks.each do |block|
|
17
|
+
block.call(group)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
group
|
21
|
+
end
|
15
22
|
|
16
23
|
# Run the application in the foreground with a simple watchdog
|
17
24
|
def run
|
@@ -31,52 +38,68 @@ module Celluloid
|
|
31
38
|
# * as: register this application in the Celluloid::Actor[] directory
|
32
39
|
# * args: start the actor with the given arguments
|
33
40
|
def supervise(klass, options = {})
|
34
|
-
|
41
|
+
blocks << lambda do |group|
|
42
|
+
group.add klass, options
|
43
|
+
end
|
35
44
|
end
|
36
45
|
|
37
46
|
# Register a pool of actors to be launched on group startup
|
38
47
|
def pool(klass)
|
39
|
-
|
48
|
+
blocks << lambda do |group|
|
49
|
+
group.pool klass
|
50
|
+
end
|
40
51
|
end
|
41
52
|
end
|
42
53
|
|
43
54
|
# Start the group
|
44
|
-
def initialize
|
45
|
-
@
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
55
|
+
def initialize(registry = nil)
|
56
|
+
@members = []
|
57
|
+
@registry = registry || Registry.root
|
58
|
+
|
59
|
+
yield self if block_given?
|
60
|
+
end
|
61
|
+
|
62
|
+
def supervise(klass, *args, &block)
|
63
|
+
add(klass, :args => args, :block => block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def supervise_as(name, klass, *args, &block)
|
67
|
+
add(klass, :args => args, :block => block, :as => name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def pool(klass)
|
71
|
+
add(klass, :method => 'pool_link')
|
72
|
+
end
|
73
|
+
|
74
|
+
def add(klass, options)
|
75
|
+
member = Member.new(@registry, klass, options)
|
76
|
+
@members << member
|
77
|
+
member
|
78
|
+
end
|
79
|
+
|
80
|
+
def actors
|
81
|
+
@members.map(&:actor)
|
53
82
|
end
|
54
83
|
|
55
84
|
# Terminate the group
|
56
85
|
def finalize
|
57
|
-
@
|
58
|
-
begin
|
59
|
-
actor.terminate
|
60
|
-
rescue DeadActorError
|
61
|
-
end
|
62
|
-
end
|
86
|
+
@members.each(&:terminate)
|
63
87
|
end
|
64
88
|
|
65
89
|
# Restart a crashed actor
|
66
90
|
def restart_actor(actor, reason)
|
67
|
-
member = @
|
91
|
+
member = @members.find do |member|
|
92
|
+
member.actor == actor
|
93
|
+
end
|
68
94
|
raise "a group member went missing. This shouldn't be!" unless member
|
69
95
|
|
70
|
-
|
71
|
-
return unless reason
|
72
|
-
|
73
|
-
actor = member.start
|
74
|
-
@actors[actor] = member
|
96
|
+
member.restart(reason)
|
75
97
|
end
|
76
98
|
|
77
99
|
# A member of the group
|
78
100
|
class Member
|
79
|
-
def initialize(klass, options = {})
|
101
|
+
def initialize(registry, klass, options = {})
|
102
|
+
@registry = registry
|
80
103
|
@klass = klass
|
81
104
|
|
82
105
|
# Stringify keys :/
|
@@ -84,12 +107,31 @@ module Celluloid
|
|
84
107
|
|
85
108
|
@name = options['as']
|
86
109
|
@args = options['args'] ? Array(options['args']) : []
|
110
|
+
@block = options['block']
|
87
111
|
@method = options['method'] || 'new_link'
|
112
|
+
|
113
|
+
start
|
88
114
|
end
|
115
|
+
attr_reader :name, :actor
|
89
116
|
|
90
117
|
def start
|
91
|
-
actor = @klass.send(@method, *@args)
|
92
|
-
|
118
|
+
@actor = @klass.send(@method, *@args, &@block)
|
119
|
+
@registry[@name] = @actor if @name
|
120
|
+
end
|
121
|
+
|
122
|
+
def restart(reason)
|
123
|
+
@actor = nil
|
124
|
+
@registry.delete(@name) if @name
|
125
|
+
|
126
|
+
# Ignore supervisors that shut down cleanly
|
127
|
+
return unless reason
|
128
|
+
|
129
|
+
start
|
130
|
+
end
|
131
|
+
|
132
|
+
def terminate
|
133
|
+
@actor.terminate if @actor
|
134
|
+
rescue DeadActorError
|
93
135
|
end
|
94
136
|
end
|
95
137
|
end
|
data/lib/celluloid/supervisor.rb
CHANGED
@@ -2,68 +2,21 @@ module Celluloid
|
|
2
2
|
# Supervisors are actors that watch over other actors and restart them if
|
3
3
|
# they crash
|
4
4
|
class Supervisor
|
5
|
-
include Celluloid
|
6
|
-
trap_exit :restart_actor
|
7
|
-
|
8
|
-
# Retrieve the actor this supervisor is supervising
|
9
|
-
attr_reader :actor
|
10
|
-
|
11
5
|
class << self
|
12
6
|
# Define the root of the supervision tree
|
13
7
|
attr_accessor :root
|
14
8
|
|
15
9
|
def supervise(klass, *args, &block)
|
16
|
-
new
|
10
|
+
SupervisionGroup.new do |group|
|
11
|
+
group.supervise klass, *args, &block
|
12
|
+
end
|
17
13
|
end
|
18
14
|
|
19
15
|
def supervise_as(name, klass, *args, &block)
|
20
|
-
new
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize(name, klass, *args, &block)
|
25
|
-
@name, @klass, @args, @block = name, klass, args, block
|
26
|
-
@started = false
|
27
|
-
|
28
|
-
start_actor
|
29
|
-
end
|
30
|
-
|
31
|
-
def finalize
|
32
|
-
@actor.terminate if @actor and @actor.alive?
|
33
|
-
end
|
34
|
-
|
35
|
-
def start_actor(start_attempts = 3, sleep_interval = 30)
|
36
|
-
failures = 0
|
37
|
-
|
38
|
-
begin
|
39
|
-
@actor = @klass.new_link(*@args, &@block)
|
40
|
-
rescue
|
41
|
-
failures += 1
|
42
|
-
if failures >= start_attempts
|
43
|
-
failures = 0
|
44
|
-
|
45
|
-
Logger.warn("#{@klass} is crashing on initialize too quickly, sleeping for #{sleep_interval} seconds")
|
46
|
-
sleep sleep_interval
|
16
|
+
SupervisionGroup.new do |group|
|
17
|
+
group.supervise_as name, klass, *args, &block
|
47
18
|
end
|
48
|
-
retry
|
49
19
|
end
|
50
|
-
|
51
|
-
@started = true
|
52
|
-
Actor[@name] = @actor if @name
|
53
|
-
end
|
54
|
-
|
55
|
-
# When actors die, regardless of the reason, restart them
|
56
|
-
def restart_actor(actor, reason)
|
57
|
-
# If the actor we're supervising exited cleanly, exit the supervisor cleanly too
|
58
|
-
terminate unless reason
|
59
|
-
|
60
|
-
start_actor if @started
|
61
|
-
end
|
62
|
-
|
63
|
-
def inspect
|
64
|
-
str = "#<#{self.class}(#{@klass}):0x#{object_id.to_s(16)}"
|
65
|
-
str << " " << @args.map { |arg| arg.inspect }.join(' ') unless @args.empty?
|
66
|
-
str << ">"
|
67
20
|
end
|
68
21
|
end
|
69
22
|
end
|
data/lib/celluloid/version.rb
CHANGED
@@ -3,83 +3,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
3
3
|
attr_accessor :foo
|
4
4
|
end
|
5
5
|
|
6
|
-
let
|
7
|
-
Class.new do
|
8
|
-
include included_module
|
9
|
-
attr_reader :name
|
10
|
-
|
11
|
-
def initialize(name)
|
12
|
-
@name = name
|
13
|
-
@delegate = [:bar]
|
14
|
-
end
|
15
|
-
|
16
|
-
def change_name(new_name)
|
17
|
-
@name = new_name
|
18
|
-
end
|
19
|
-
|
20
|
-
def change_name_async(new_name)
|
21
|
-
change_name! new_name
|
22
|
-
end
|
23
|
-
|
24
|
-
def greet
|
25
|
-
"Hi, I'm #{@name}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def run(*args)
|
29
|
-
yield(*args)
|
30
|
-
end
|
31
|
-
|
32
|
-
def crash
|
33
|
-
raise ExampleCrash, "the spec purposely crashed me :("
|
34
|
-
end
|
35
|
-
|
36
|
-
def crash_with_abort(reason, foo = nil)
|
37
|
-
example_crash = ExampleCrash.new(reason)
|
38
|
-
example_crash.foo = foo
|
39
|
-
abort example_crash
|
40
|
-
end
|
41
|
-
|
42
|
-
def crash_with_abort_raw(reason)
|
43
|
-
abort reason
|
44
|
-
end
|
45
|
-
|
46
|
-
def internal_hello
|
47
|
-
external_hello
|
48
|
-
end
|
49
|
-
|
50
|
-
def external_hello
|
51
|
-
"Hello"
|
52
|
-
end
|
53
|
-
|
54
|
-
def method_missing(method_name, *args, &block)
|
55
|
-
if delegates?(method_name)
|
56
|
-
@delegate.send method_name, *args, &block
|
57
|
-
else
|
58
|
-
super
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def respond_to?(method_name)
|
63
|
-
super || delegates?(method_name)
|
64
|
-
end
|
65
|
-
|
66
|
-
def call_private
|
67
|
-
zomg_private!
|
68
|
-
end
|
69
|
-
|
70
|
-
def zomg_private
|
71
|
-
@private_called = true
|
72
|
-
end
|
73
|
-
private :zomg_private
|
74
|
-
attr_reader :private_called
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def delegates?(method_name)
|
79
|
-
@delegate.respond_to?(method_name)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
6
|
+
let(:actor_class) { ExampleActorClass.create(included_module) }
|
83
7
|
|
84
8
|
it "returns the actor's class, not the proxy's" do
|
85
9
|
actor = actor_class.new "Troy McClure"
|
@@ -417,6 +341,22 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
417
341
|
end
|
418
342
|
end
|
419
343
|
|
344
|
+
context :exclusive do
|
345
|
+
subject do
|
346
|
+
Class.new do
|
347
|
+
include included_module
|
348
|
+
def exclusive_example
|
349
|
+
exclusive?
|
350
|
+
end
|
351
|
+
exclusive :exclusive_example
|
352
|
+
end.new
|
353
|
+
end
|
354
|
+
|
355
|
+
it "supports exclusive methods" do
|
356
|
+
subject.exclusive_example.should be_true
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
420
360
|
context :receiving do
|
421
361
|
before do
|
422
362
|
@receiver = Class.new do
|
@@ -446,11 +386,14 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
446
386
|
started_at = Time.now
|
447
387
|
|
448
388
|
receiver.receive(interval) { false }.should be_nil
|
449
|
-
(Time.now - started_at).should be_within(
|
389
|
+
(Time.now - started_at).should be_within(Q).of interval
|
450
390
|
end
|
451
391
|
end
|
452
392
|
|
453
393
|
context :timers do
|
394
|
+
# Level of accuracy enforced by the tests (50ms)
|
395
|
+
Q = 0.05
|
396
|
+
|
454
397
|
before do
|
455
398
|
@klass = Class.new do
|
456
399
|
include included_module
|
@@ -487,7 +430,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
487
430
|
|
488
431
|
# Sleep long enough to ensure we're actually seeing behavior when asleep
|
489
432
|
# but not so long as to delay the test suite unnecessarily
|
490
|
-
interval =
|
433
|
+
interval = Q * 10
|
491
434
|
started_at = Time.now
|
492
435
|
|
493
436
|
future = actor.future(:do_sleep, interval)
|
@@ -495,49 +438,49 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
495
438
|
actor.should be_sleeping
|
496
439
|
|
497
440
|
future.value
|
498
|
-
(Time.now - started_at).should be_within(
|
441
|
+
(Time.now - started_at).should be_within(Q).of interval
|
499
442
|
end
|
500
443
|
|
501
444
|
it "schedules timers which fire in the future" do
|
502
445
|
actor = @klass.new
|
503
446
|
|
504
|
-
interval =
|
447
|
+
interval = Q * 10
|
505
448
|
started_at = Time.now
|
506
449
|
|
507
450
|
timer = actor.fire_after(interval)
|
508
451
|
actor.should_not be_fired
|
509
452
|
|
510
|
-
sleep(interval +
|
453
|
+
sleep(interval + Q) # wonky! #/
|
511
454
|
actor.should be_fired
|
512
455
|
end
|
513
456
|
|
514
457
|
it "schedules recurring timers which fire in the future" do
|
515
458
|
actor = @klass.new
|
516
459
|
|
517
|
-
interval =
|
460
|
+
interval = Q * 10
|
518
461
|
started_at = Time.now
|
519
462
|
|
520
463
|
timer = actor.fire_every(interval)
|
521
464
|
actor.fired.should be == 0
|
522
465
|
|
523
|
-
sleep(interval +
|
466
|
+
sleep(interval + Q) # wonky! #/
|
524
467
|
actor.fired.should be == 1
|
525
468
|
|
526
|
-
2.times { sleep(interval +
|
469
|
+
2.times { sleep(interval + Q) } # wonky! #/
|
527
470
|
actor.fired.should be == 3
|
528
471
|
end
|
529
472
|
|
530
473
|
it "cancels timers before they fire" do
|
531
474
|
actor = @klass.new
|
532
475
|
|
533
|
-
interval =
|
476
|
+
interval = Q * 10
|
534
477
|
started_at = Time.now
|
535
478
|
|
536
479
|
timer = actor.fire_after(interval)
|
537
480
|
actor.should_not be_fired
|
538
481
|
timer.cancel
|
539
482
|
|
540
|
-
sleep(interval +
|
483
|
+
sleep(interval + Q) # wonky! #/
|
541
484
|
actor.should_not be_fired
|
542
485
|
end
|
543
486
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ExampleActorClass
|
2
|
+
def self.create(included_module)
|
3
|
+
Class.new do
|
4
|
+
include included_module
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@delegate = [:bar]
|
10
|
+
end
|
11
|
+
|
12
|
+
def change_name(new_name)
|
13
|
+
@name = new_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def change_name_async(new_name)
|
17
|
+
change_name! new_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def greet
|
21
|
+
"Hi, I'm #{@name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(*args)
|
25
|
+
yield(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def crash
|
29
|
+
raise ExampleCrash, "the spec purposely crashed me :("
|
30
|
+
end
|
31
|
+
|
32
|
+
def crash_with_abort(reason, foo = nil)
|
33
|
+
example_crash = ExampleCrash.new(reason)
|
34
|
+
example_crash.foo = foo
|
35
|
+
abort example_crash
|
36
|
+
end
|
37
|
+
|
38
|
+
def crash_with_abort_raw(reason)
|
39
|
+
abort reason
|
40
|
+
end
|
41
|
+
|
42
|
+
def internal_hello
|
43
|
+
external_hello
|
44
|
+
end
|
45
|
+
|
46
|
+
def external_hello
|
47
|
+
"Hello"
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_missing(method_name, *args, &block)
|
51
|
+
if delegates?(method_name)
|
52
|
+
@delegate.send method_name, *args, &block
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def respond_to?(method_name)
|
59
|
+
super || delegates?(method_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def call_private
|
63
|
+
zomg_private!
|
64
|
+
end
|
65
|
+
|
66
|
+
def zomg_private
|
67
|
+
@private_called = true
|
68
|
+
end
|
69
|
+
private :zomg_private
|
70
|
+
attr_reader :private_called
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def delegates?(method_name)
|
75
|
+
@delegate.respond_to?(method_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
shared_context "a Celluloid Mailbox" do
|
2
|
+
# Level of timer accuracy enforced by the tests (50ms)
|
3
|
+
Q = 0.05
|
4
|
+
|
2
5
|
class TestEvent < Celluloid::SystemEvent; end
|
3
6
|
|
4
7
|
it "receives messages" do
|
@@ -47,6 +50,6 @@ shared_context "a Celluloid Mailbox" do
|
|
47
50
|
started_at = Time.now
|
48
51
|
|
49
52
|
subject.receive(interval) { false }
|
50
|
-
(Time.now - started_at).should be_within(
|
53
|
+
(Time.now - started_at).should be_within(Q).of interval
|
51
54
|
end
|
52
55
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: celluloid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
requirement: &
|
15
|
+
name: timers
|
16
|
+
requirement: &70167227984140 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
22
|
-
type: :
|
21
|
+
version: 1.0.0
|
22
|
+
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70167227984140
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
27
|
-
requirement: &
|
26
|
+
name: rake
|
27
|
+
requirement: &70167227983760 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70167227983760
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
38
|
-
requirement: &
|
37
|
+
name: rspec
|
38
|
+
requirement: &70167227983300 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70167227983300
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: benchmark_suite
|
49
|
-
requirement: &
|
49
|
+
requirement: &70167227982880 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70167227982880
|
58
58
|
description: Celluloid enables people to build concurrent programs out of concurrent
|
59
59
|
objects just as easily as they build sequential programs out of sequential objects
|
60
60
|
email:
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- lib/celluloid/links.rb
|
78
78
|
- lib/celluloid/logger.rb
|
79
79
|
- lib/celluloid/mailbox.rb
|
80
|
+
- lib/celluloid/notifications.rb
|
80
81
|
- lib/celluloid/pool_manager.rb
|
81
82
|
- lib/celluloid/receivers.rb
|
82
83
|
- lib/celluloid/registry.rb
|
@@ -87,11 +88,11 @@ files:
|
|
87
88
|
- lib/celluloid/supervisor.rb
|
88
89
|
- lib/celluloid/task.rb
|
89
90
|
- lib/celluloid/thread_handle.rb
|
90
|
-
- lib/celluloid/timers.rb
|
91
91
|
- lib/celluloid/uuid.rb
|
92
92
|
- lib/celluloid/version.rb
|
93
93
|
- lib/celluloid.rb
|
94
94
|
- spec/support/actor_examples.rb
|
95
|
+
- spec/support/example_actor_class.rb
|
95
96
|
- spec/support/mailbox_examples.rb
|
96
97
|
homepage: https://github.com/celluloid/celluloid
|
97
98
|
licenses:
|
data/lib/celluloid/timers.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
module Celluloid
|
2
|
-
# Low precision timers implemented in pure Ruby
|
3
|
-
class Timers
|
4
|
-
def initialize
|
5
|
-
@timers = []
|
6
|
-
end
|
7
|
-
|
8
|
-
# Call the given block after the given interval
|
9
|
-
def add(interval, recurring = false, &block)
|
10
|
-
Timer.new(self, interval, recurring, block)
|
11
|
-
end
|
12
|
-
|
13
|
-
# Wait for the next timer and fire it
|
14
|
-
def wait
|
15
|
-
return if @timers.empty?
|
16
|
-
|
17
|
-
interval = wait_interval
|
18
|
-
sleep interval if interval >= Timer::QUANTUM
|
19
|
-
fire
|
20
|
-
end
|
21
|
-
|
22
|
-
# Interval to wait until when the next timer will fire
|
23
|
-
def wait_interval
|
24
|
-
@timers.first.time - Time.now unless empty?
|
25
|
-
end
|
26
|
-
|
27
|
-
# Fire all timers that are ready
|
28
|
-
def fire
|
29
|
-
return if @timers.empty?
|
30
|
-
|
31
|
-
time = Time.now + Timer::QUANTUM
|
32
|
-
while not empty? and time > @timers.first.time
|
33
|
-
timer = @timers.shift
|
34
|
-
timer.call
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Insert a timer into the active timers
|
39
|
-
def insert(timer)
|
40
|
-
@timers.insert(index(timer), timer)
|
41
|
-
end
|
42
|
-
|
43
|
-
# Remove a given timer from the set we're monitoring
|
44
|
-
def cancel(timer)
|
45
|
-
@timers.delete timer
|
46
|
-
end
|
47
|
-
|
48
|
-
# Are there any timers pending?
|
49
|
-
def empty?
|
50
|
-
@timers.empty?
|
51
|
-
end
|
52
|
-
|
53
|
-
# Index where a timer would be located in the sorted timers array
|
54
|
-
def index(timer)
|
55
|
-
l, r = 0, @timers.size - 1
|
56
|
-
|
57
|
-
while l <= r
|
58
|
-
m = (r + l) / 2
|
59
|
-
if timer < @timers.at(m)
|
60
|
-
r = m - 1
|
61
|
-
else
|
62
|
-
l = m + 1
|
63
|
-
end
|
64
|
-
end
|
65
|
-
l
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# An individual timer set to fire a given proc at a given time
|
70
|
-
class Timer
|
71
|
-
include Comparable
|
72
|
-
|
73
|
-
# The timer system is guaranteed (at least by the specs) to be this precise
|
74
|
-
# during normal operation. Long blocking calls within actors will delay the
|
75
|
-
# firing of timers
|
76
|
-
QUANTUM = 0.02
|
77
|
-
|
78
|
-
attr_reader :interval, :time, :recurring
|
79
|
-
|
80
|
-
def initialize(timers, interval, recurring, block)
|
81
|
-
@timers, @interval, @recurring = timers, interval, recurring
|
82
|
-
@block = block
|
83
|
-
|
84
|
-
reset
|
85
|
-
end
|
86
|
-
|
87
|
-
def <=>(other)
|
88
|
-
@time <=> other.time
|
89
|
-
end
|
90
|
-
|
91
|
-
# Cancel this timer
|
92
|
-
def cancel
|
93
|
-
@timers.cancel self
|
94
|
-
end
|
95
|
-
|
96
|
-
# Reset this timer
|
97
|
-
def reset
|
98
|
-
@timers.cancel self if defined?(@time)
|
99
|
-
@time = Time.now + @interval
|
100
|
-
@timers.insert self
|
101
|
-
end
|
102
|
-
|
103
|
-
# Fire the block
|
104
|
-
def fire
|
105
|
-
reset if recurring
|
106
|
-
@block.call
|
107
|
-
end
|
108
|
-
alias_method :call, :fire
|
109
|
-
end
|
110
|
-
end
|