celluloid 0.11.0 → 0.11.1
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 +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
|