celluloid 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +17 -7
- data/lib/celluloid.rb +50 -6
- data/lib/celluloid/actor.rb +20 -6
- data/lib/celluloid/actor_proxy.rb +29 -6
- data/lib/celluloid/calls.rb +2 -2
- data/lib/celluloid/core_ext.rb +3 -3
- data/lib/celluloid/events.rb +13 -4
- data/lib/celluloid/fiber.rb +2 -3
- data/lib/celluloid/future.rb +8 -4
- data/lib/celluloid/{thread_pool.rb → internal_pool.rb} +8 -6
- data/lib/celluloid/links.rb +5 -23
- data/lib/celluloid/pool_manager.rb +62 -0
- data/lib/celluloid/registry.rb +2 -0
- data/lib/celluloid/supervision_group.rb +99 -0
- data/lib/celluloid/supervisor.rb +14 -5
- data/lib/celluloid/task.rb +1 -4
- data/lib/celluloid/thread_handle.rb +38 -0
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +70 -19
- metadata +16 -14
- data/lib/celluloid/group.rb +0 -85
- data/lib/celluloid/pool.rb +0 -105
data/README.md
CHANGED
@@ -72,18 +72,28 @@ 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
|
+
### Is It "Production Ready™"?
|
80
|
+
|
81
|
+
Yes, many users are now running Celluloid in production by using
|
82
|
+
[Sidekiq](https://github.com/mperham/sidekiq)
|
83
|
+
|
75
84
|
Supported Platforms
|
76
85
|
-------------------
|
77
86
|
|
78
|
-
Celluloid works on Ruby 1.9.3, JRuby 1.6
|
79
|
-
|
80
|
-
|
81
|
-
|
87
|
+
Celluloid works on Ruby 1.9.3, JRuby 1.6, and Rubinius 2.0. JRuby or Rubinius
|
88
|
+
are the preferred platforms as they support true thread-level parallelism when
|
89
|
+
executing Ruby code, whereas MRI/YARV is constrained by a global interpreter
|
90
|
+
lock (GIL) and can only execute one thread at a time.
|
82
91
|
|
83
|
-
|
84
|
-
|
92
|
+
Celluloid requires Ruby 1.9 mode on all interpreters. This works out of the
|
93
|
+
box on MRI/YARV, and requires the following flags elsewhere:
|
85
94
|
|
86
|
-
|
95
|
+
* JRuby: --1.9 command line option, or JRUBY_OPTS=--1.9 environment variable
|
96
|
+
* rbx: -X19 command line option
|
87
97
|
|
88
98
|
Additional Reading
|
89
99
|
------------------
|
data/lib/celluloid.rb
CHANGED
@@ -73,8 +73,11 @@ module Celluloid
|
|
73
73
|
actors = Actor.all
|
74
74
|
Logger.info "Terminating #{actors.size} actors..." if actors.size > 0
|
75
75
|
|
76
|
+
# Attempt to shut down the supervision tree, if available
|
77
|
+
Supervisor.root.terminate if Supervisor.root
|
78
|
+
|
76
79
|
# Actors cannot self-terminate, you must do it for them
|
77
|
-
terminators =
|
80
|
+
terminators = Actor.all.each do |actor|
|
78
81
|
begin
|
79
82
|
actor.future(:terminate)
|
80
83
|
rescue DeadActorError, MailboxError
|
@@ -130,6 +133,25 @@ module Celluloid
|
|
130
133
|
Supervisor.supervise_as(name, self, *args, &block)
|
131
134
|
end
|
132
135
|
|
136
|
+
# Create a new pool of workers. Accepts the following options:
|
137
|
+
#
|
138
|
+
# * size: how many workers to create. Default is worker per CPU core
|
139
|
+
# * args: array of arguments to pass when creating a worker
|
140
|
+
#
|
141
|
+
def pool(options = {})
|
142
|
+
PoolManager.new(self, options)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Same as pool, but links to the pool manager
|
146
|
+
def pool_link(options = {})
|
147
|
+
PoolManager.new_link(self, options)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Run an actor in the foreground
|
151
|
+
def run(*args, &block)
|
152
|
+
new(*args, &block).join
|
153
|
+
end
|
154
|
+
|
133
155
|
# Trap errors from actors we're linked to when they exit
|
134
156
|
def trap_exit(callback)
|
135
157
|
@exit_handler = callback.to_sym
|
@@ -174,6 +196,11 @@ module Celluloid
|
|
174
196
|
|
175
197
|
# Raise an exception in caller context, but stay running
|
176
198
|
def abort(cause)
|
199
|
+
cause = case cause
|
200
|
+
when String then RuntimeError.new(cause)
|
201
|
+
when Exception then cause
|
202
|
+
else raise TypeError, "Exception object/String expected, but #{cause.class} received"
|
203
|
+
end
|
177
204
|
raise AbortError.new(cause)
|
178
205
|
end
|
179
206
|
|
@@ -207,6 +234,11 @@ module Celluloid
|
|
207
234
|
Actor.current
|
208
235
|
end
|
209
236
|
|
237
|
+
# Obtain the name of the current actor
|
238
|
+
def name
|
239
|
+
Actor.name
|
240
|
+
end
|
241
|
+
|
210
242
|
# Obtain the running tasks for this actor
|
211
243
|
def tasks
|
212
244
|
Thread.current[:actor].tasks.to_a
|
@@ -280,17 +312,28 @@ module Celluloid
|
|
280
312
|
# messages in its mailbox in the meantime
|
281
313
|
def defer(&block)
|
282
314
|
# This implementation relies on the present implementation of
|
283
|
-
# Celluloid::Future, which uses
|
315
|
+
# Celluloid::Future, which uses a thread from InternalPool to run the block
|
284
316
|
Future.new(&block).value
|
285
317
|
end
|
286
318
|
|
319
|
+
# Handle async calls within an actor itself
|
320
|
+
def async(meth, *args, &block)
|
321
|
+
Actor.async Thread.current[:actor].mailbox, meth, *args, &block
|
322
|
+
end
|
323
|
+
|
324
|
+
# Handle calls to future within an actor itself
|
325
|
+
def future(meth, *args, &block)
|
326
|
+
Actor.future Thread.current[:actor].mailbox, meth, *args, &block
|
327
|
+
end
|
328
|
+
|
287
329
|
# Process async calls via method_missing
|
288
330
|
def method_missing(meth, *args, &block)
|
289
331
|
# bang methods are async calls
|
290
332
|
if meth.to_s.match(/!$/)
|
291
333
|
unbanged_meth = meth.to_s.sub(/!$/, '')
|
292
|
-
|
334
|
+
args.unshift unbanged_meth
|
293
335
|
|
336
|
+
call = AsyncCall.new(nil, :__send__, args, block)
|
294
337
|
begin
|
295
338
|
Thread.current[:actor].mailbox << call
|
296
339
|
rescue MailboxError
|
@@ -315,20 +358,21 @@ require 'celluloid/cpu_counter'
|
|
315
358
|
require 'celluloid/events'
|
316
359
|
require 'celluloid/fiber'
|
317
360
|
require 'celluloid/fsm'
|
361
|
+
require 'celluloid/internal_pool'
|
318
362
|
require 'celluloid/links'
|
319
363
|
require 'celluloid/logger'
|
320
364
|
require 'celluloid/mailbox'
|
321
|
-
require 'celluloid/pool'
|
322
365
|
require 'celluloid/receivers'
|
323
366
|
require 'celluloid/registry'
|
324
367
|
require 'celluloid/responses'
|
325
368
|
require 'celluloid/signals'
|
326
369
|
require 'celluloid/task'
|
370
|
+
require 'celluloid/thread_handle'
|
327
371
|
require 'celluloid/timers'
|
328
|
-
require 'celluloid/thread_pool'
|
329
372
|
require 'celluloid/uuid'
|
330
373
|
|
331
374
|
require 'celluloid/actor'
|
332
375
|
require 'celluloid/future'
|
333
|
-
require 'celluloid/
|
376
|
+
require 'celluloid/pool_manager'
|
377
|
+
require 'celluloid/supervision_group'
|
334
378
|
require 'celluloid/supervisor'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -20,7 +20,7 @@ module Celluloid
|
|
20
20
|
# messages.
|
21
21
|
class Actor
|
22
22
|
extend Registry
|
23
|
-
attr_reader :proxy, :tasks, :links, :mailbox
|
23
|
+
attr_reader :subject, :proxy, :tasks, :links, :mailbox, :thread, :name
|
24
24
|
|
25
25
|
class << self
|
26
26
|
# Obtain the current actor
|
@@ -29,7 +29,14 @@ module Celluloid
|
|
29
29
|
raise NotActorError, "not in actor scope" unless actor
|
30
30
|
actor.proxy
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
|
+
# Obtain the name of the current actor
|
34
|
+
def name
|
35
|
+
actor = Thread.current[:actor]
|
36
|
+
raise NotActorError, "not in actor scope" unless actor
|
37
|
+
actor.name
|
38
|
+
end
|
39
|
+
|
33
40
|
# Invoke a method on the given actor via its mailbox
|
34
41
|
def call(mailbox, meth, *args, &block)
|
35
42
|
call = SyncCall.new(Thread.mailbox, meth, args, block)
|
@@ -87,7 +94,6 @@ module Celluloid
|
|
87
94
|
def initialize(subject)
|
88
95
|
@subject = subject
|
89
96
|
@mailbox = subject.class.mailbox_factory
|
90
|
-
@proxy = ActorProxy.new(@mailbox, subject.class.to_s)
|
91
97
|
@tasks = Set.new
|
92
98
|
@links = Links.new
|
93
99
|
@signals = Signals.new
|
@@ -95,12 +101,15 @@ module Celluloid
|
|
95
101
|
@timers = Timers.new
|
96
102
|
@running = true
|
97
103
|
@exclusive = false
|
104
|
+
@name = nil
|
98
105
|
|
99
|
-
@thread =
|
106
|
+
@thread = ThreadHandle.new do
|
100
107
|
Thread.current[:actor] = self
|
101
108
|
Thread.current[:mailbox] = @mailbox
|
102
109
|
run
|
103
110
|
end
|
111
|
+
|
112
|
+
@proxy = ActorProxy.new(self)
|
104
113
|
end
|
105
114
|
|
106
115
|
# Is this actor running in exclusive mode?
|
@@ -145,6 +154,9 @@ module Celluloid
|
|
145
154
|
rescue ExitEvent => exit_event
|
146
155
|
Task.new(:exit_handler) { handle_exit_event exit_event }.resume
|
147
156
|
retry
|
157
|
+
rescue NamingRequest => ex
|
158
|
+
@name = ex.name
|
159
|
+
retry
|
148
160
|
rescue TerminationRequest
|
149
161
|
break
|
150
162
|
end
|
@@ -162,8 +174,9 @@ module Celluloid
|
|
162
174
|
end
|
163
175
|
|
164
176
|
shutdown
|
165
|
-
rescue => ex
|
177
|
+
rescue Exception => ex
|
166
178
|
handle_crash(ex)
|
179
|
+
raise unless ex.is_a? StandardError
|
167
180
|
end
|
168
181
|
|
169
182
|
# How long to wait until the next timer fires
|
@@ -249,7 +262,8 @@ module Celluloid
|
|
249
262
|
|
250
263
|
# Run the user-defined finalizer, if one is set
|
251
264
|
def run_finalizer
|
252
|
-
|
265
|
+
return unless @subject.respond_to? :finalize
|
266
|
+
Task.new(:finalizer) { @subject.finalize }.resume
|
253
267
|
rescue => ex
|
254
268
|
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
255
269
|
end
|
@@ -5,8 +5,8 @@ module Celluloid
|
|
5
5
|
class ActorProxy
|
6
6
|
attr_reader :mailbox
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@mailbox, @klass = mailbox,
|
8
|
+
def initialize(actor)
|
9
|
+
@mailbox, @thread, @klass = actor.mailbox, actor.thread, actor.subject.class.to_s
|
10
10
|
end
|
11
11
|
|
12
12
|
def _send_(meth, *args, &block)
|
@@ -17,6 +17,10 @@ module Celluloid
|
|
17
17
|
Actor.call @mailbox, :__send__, :class
|
18
18
|
end
|
19
19
|
|
20
|
+
def name
|
21
|
+
Actor.call @mailbox, :name
|
22
|
+
end
|
23
|
+
|
20
24
|
def is_a?(klass)
|
21
25
|
Actor.call @mailbox, :is_a?, klass
|
22
26
|
end
|
@@ -47,6 +51,12 @@ module Celluloid
|
|
47
51
|
"#<Celluloid::Actor(#{@klass}) dead>"
|
48
52
|
end
|
49
53
|
|
54
|
+
# Make an asynchronous call to an actor, for those who don't like the
|
55
|
+
# predicate syntax. TIMTOWTDI!
|
56
|
+
def async(method_name, *args, &block)
|
57
|
+
Actor.async @mailbox, method_name, *args, &block
|
58
|
+
end
|
59
|
+
|
50
60
|
# Create a Celluloid::Future which calls a given method
|
51
61
|
def future(method_name, *args, &block)
|
52
62
|
Actor.future @mailbox, method_name, *args, &block
|
@@ -64,14 +74,27 @@ module Celluloid
|
|
64
74
|
@mailbox.system_event TerminationRequest.new
|
65
75
|
end
|
66
76
|
|
77
|
+
# Forcibly kill a given actor
|
78
|
+
def kill
|
79
|
+
@thread.kill
|
80
|
+
begin
|
81
|
+
@mailbox.shutdown
|
82
|
+
rescue DeadActorError
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Wait for an actor to terminate
|
87
|
+
def join
|
88
|
+
@thread.join
|
89
|
+
end
|
90
|
+
|
67
91
|
# method_missing black magic to call bang predicate methods asynchronously
|
68
92
|
def method_missing(meth, *args, &block)
|
69
|
-
meth = meth.to_s
|
70
|
-
|
71
93
|
# bang methods are async calls
|
72
94
|
if meth.match(/!$/)
|
73
|
-
meth.
|
74
|
-
|
95
|
+
unbanged_meth = meth.to_s
|
96
|
+
unbanged_meth.slice!(-1, 1)
|
97
|
+
Actor.async @mailbox, unbanged_meth, *args, &block
|
75
98
|
else
|
76
99
|
Actor.call @mailbox, meth, *args, &block
|
77
100
|
end
|
data/lib/celluloid/calls.rb
CHANGED
@@ -91,14 +91,14 @@ module Celluloid
|
|
91
91
|
begin
|
92
92
|
check_signature(obj)
|
93
93
|
rescue Exception => ex
|
94
|
-
Logger.crash("#{obj.class}: async call failed!", ex)
|
94
|
+
Logger.crash("#{obj.class}: async call `#{@method}' failed!", ex)
|
95
95
|
return
|
96
96
|
end
|
97
97
|
|
98
98
|
obj.send(@method, *@arguments, &@block)
|
99
99
|
rescue AbortError => ex
|
100
100
|
# Swallow aborted async calls, as they indicate the caller made a mistake
|
101
|
-
Logger.crash("#{obj.class}: async call aborted!", ex)
|
101
|
+
Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex)
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
data/lib/celluloid/core_ext.rb
CHANGED
@@ -10,11 +10,11 @@ class Thread
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# Receive a message either as an actor or through the local mailbox
|
13
|
-
def self.receive(&block)
|
13
|
+
def self.receive(timeout = nil, &block)
|
14
14
|
if Celluloid.actor?
|
15
|
-
Celluloid.receive(&block)
|
15
|
+
Celluloid.receive(timeout, &block)
|
16
16
|
else
|
17
|
-
mailbox.receive(&block)
|
17
|
+
mailbox.receive(timeout, &block)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/celluloid/events.rb
CHANGED
@@ -1,17 +1,26 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Exceptional system events which need to be processed out of band
|
3
3
|
class SystemEvent < Exception; end
|
4
|
-
|
4
|
+
|
5
5
|
# An actor has exited for the given reason
|
6
6
|
class ExitEvent < SystemEvent
|
7
7
|
attr_reader :actor, :reason
|
8
|
-
|
8
|
+
|
9
9
|
def initialize(actor, reason = nil)
|
10
10
|
@actor, @reason = actor, reason
|
11
11
|
super reason.to_s
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
|
+
# Name an actor at the time it's registered
|
16
|
+
class NamingRequest < SystemEvent
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def initialize(name)
|
20
|
+
@name = name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
15
24
|
# Request for an actor to terminate
|
16
25
|
class TerminationRequest < SystemEvent; end
|
17
|
-
end
|
26
|
+
end
|
data/lib/celluloid/fiber.rb
CHANGED
@@ -24,9 +24,8 @@ rescue LoadError => ex
|
|
24
24
|
# Just in case subsequent JRuby releases have broken fibers :/
|
25
25
|
raise ex
|
26
26
|
end
|
27
|
-
elsif defined?
|
28
|
-
|
29
|
-
Fiber = Rubinius::Fiber
|
27
|
+
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
|
28
|
+
raise LoadError, "Celluloid requires Rubinius 1.9 mode. Please pass the -X19 flag."
|
30
29
|
else
|
31
30
|
raise ex
|
32
31
|
end
|
data/lib/celluloid/future.rb
CHANGED
@@ -13,7 +13,7 @@ module Celluloid
|
|
13
13
|
|
14
14
|
if block
|
15
15
|
@call = SyncCall.new(self, :call, args)
|
16
|
-
|
16
|
+
InternalPool.get do
|
17
17
|
begin
|
18
18
|
@call.dispatch(block)
|
19
19
|
rescue
|
@@ -36,7 +36,7 @@ module Celluloid
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# Obtain the value for this Future
|
39
|
-
def value
|
39
|
+
def value(timeout = nil)
|
40
40
|
ready = result = nil
|
41
41
|
|
42
42
|
begin
|
@@ -61,12 +61,16 @@ module Celluloid
|
|
61
61
|
end
|
62
62
|
|
63
63
|
unless ready
|
64
|
-
result = Thread.receive do |msg|
|
64
|
+
result = Thread.receive(timeout) do |msg|
|
65
65
|
msg.is_a?(Future::Result) && msg.future == self
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
result
|
69
|
+
if result
|
70
|
+
result.value
|
71
|
+
else
|
72
|
+
raise "Timed out"
|
73
|
+
end
|
70
74
|
end
|
71
75
|
alias_method :call, :value
|
72
76
|
|
@@ -2,7 +2,7 @@ require 'thread'
|
|
2
2
|
|
3
3
|
module Celluloid
|
4
4
|
# Maintain a thread pool FOR SPEED!!
|
5
|
-
module
|
5
|
+
module InternalPool
|
6
6
|
@pool = []
|
7
7
|
@mutex = Mutex.new
|
8
8
|
|
@@ -15,11 +15,13 @@ module Celluloid
|
|
15
15
|
# Get a thread from the pool, running the given block
|
16
16
|
def get(&block)
|
17
17
|
@mutex.synchronize do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
begin
|
19
|
+
if @pool.empty?
|
20
|
+
thread = create
|
21
|
+
else
|
22
|
+
thread = @pool.shift
|
23
|
+
end
|
24
|
+
end until thread.status # handle crashed threads
|
23
25
|
|
24
26
|
thread[:queue] << block
|
25
27
|
thread
|
data/lib/celluloid/links.rb
CHANGED
@@ -1,50 +1,32 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
3
|
module Celluloid
|
4
|
-
#
|
4
|
+
# Linked actors send each other system events
|
5
5
|
class Links
|
6
6
|
include Enumerable
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@links = {}
|
10
|
-
@lock = Mutex.new
|
11
10
|
end
|
12
11
|
|
13
12
|
# Add an actor to the current links
|
14
13
|
def <<(actor)
|
15
|
-
@
|
16
|
-
@links[actor.mailbox.address] = actor
|
17
|
-
end
|
18
|
-
actor
|
14
|
+
@links[actor.mailbox.address] = actor
|
19
15
|
end
|
20
16
|
|
21
17
|
# Do links include the given actor?
|
22
18
|
def include?(actor)
|
23
|
-
@
|
24
|
-
@links.has_key? actor.mailbox.address
|
25
|
-
end
|
19
|
+
@links.has_key? actor.mailbox.address
|
26
20
|
end
|
27
21
|
|
28
22
|
# Remove an actor from the links
|
29
23
|
def delete(actor)
|
30
|
-
@
|
31
|
-
@links.delete actor.mailbox.address
|
32
|
-
end
|
33
|
-
actor
|
24
|
+
@links.delete actor.mailbox.address
|
34
25
|
end
|
35
26
|
|
36
27
|
# Iterate through all links
|
37
28
|
def each
|
38
|
-
@
|
39
|
-
@links.each { |_, actor| yield(actor) }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Map across links
|
44
|
-
def map
|
45
|
-
result = []
|
46
|
-
each { |actor| result << yield(actor) }
|
47
|
-
result
|
29
|
+
@links.each { |_, actor| yield(actor) }
|
48
30
|
end
|
49
31
|
|
50
32
|
# Send an event message to all actors
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Manages a fixed-size pool of workers
|
3
|
+
# Delegates work (i.e. methods) and supervises workers
|
4
|
+
class PoolManager
|
5
|
+
include Celluloid
|
6
|
+
trap_exit :crash_handler
|
7
|
+
|
8
|
+
def initialize(worker_class, options = {})
|
9
|
+
@size = options[:size]
|
10
|
+
raise ArgumentError, "minimum pool size is 2" if @size && @size < 2
|
11
|
+
|
12
|
+
@size ||= [Celluloid.cores, 2].max
|
13
|
+
@args = options[:args] ? Array(options[:args]) : []
|
14
|
+
|
15
|
+
@worker_class = worker_class
|
16
|
+
@idle = @size.times.map { worker_class.new_link(*@args) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Execute the given method within a worker
|
20
|
+
def execute(method, *args, &block)
|
21
|
+
worker = provision_worker
|
22
|
+
|
23
|
+
begin
|
24
|
+
worker._send_ method, *args, &block
|
25
|
+
rescue => ex
|
26
|
+
abort ex
|
27
|
+
ensure
|
28
|
+
@idle << worker if worker.alive?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Provision a new worker
|
33
|
+
def provision_worker
|
34
|
+
while @idle.empty?
|
35
|
+
# Using exclusive mode blocks incoming messages, so they don't pile
|
36
|
+
# up as waiting Celluloid::Tasks
|
37
|
+
response = exclusive { receive { |msg| msg.is_a? Response } }
|
38
|
+
Thread.current[:actor].handle_message(response)
|
39
|
+
end
|
40
|
+
@idle.shift
|
41
|
+
end
|
42
|
+
|
43
|
+
# Spawn a new worker for every crashed one
|
44
|
+
def crash_handler(actor, reason)
|
45
|
+
@idle.delete actor
|
46
|
+
return unless reason # don't restart workers that exit cleanly
|
47
|
+
@idle << @worker_class.new_link(*@args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def respond_to?(method)
|
51
|
+
super || (@worker_class ? @worker_class.instance_methods.include?(method.to_sym) : false)
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_missing(method, *args, &block)
|
55
|
+
if respond_to?(method)
|
56
|
+
execute method, *args, &block
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/celluloid/registry.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Supervise collections of actors as a group
|
3
|
+
class SupervisionGroup
|
4
|
+
include Celluloid
|
5
|
+
trap_exit :restart_actor
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Actors or sub-applications to be supervised
|
9
|
+
def members
|
10
|
+
@members ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Start this application (and watch it with a supervisor)
|
14
|
+
alias_method :run!, :supervise
|
15
|
+
|
16
|
+
# Run the application in the foreground with a simple watchdog
|
17
|
+
def run
|
18
|
+
loop do
|
19
|
+
supervisor = run!
|
20
|
+
|
21
|
+
# Take five, toplevel supervisor
|
22
|
+
sleep 5 while supervisor.alive?
|
23
|
+
|
24
|
+
Logger.error "!!! Celluloid::Group #{self} crashed. Restarting..."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register an actor class or a sub-group to be launched and supervised
|
29
|
+
# Available options are:
|
30
|
+
#
|
31
|
+
# * as: register this application in the Celluloid::Actor[] directory
|
32
|
+
# * args: start the actor with the given arguments
|
33
|
+
def supervise(klass, options = {})
|
34
|
+
members << Member.new(klass, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Register a pool of actors to be launched on group startup
|
38
|
+
def pool(klass)
|
39
|
+
members << Member.new(klass, method: 'pool_link')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Start the group
|
44
|
+
def initialize
|
45
|
+
@actors = {}
|
46
|
+
|
47
|
+
# This is some serious lolcode, but like... start the supervisors for
|
48
|
+
# this group
|
49
|
+
self.class.members.each do |member|
|
50
|
+
actor = member.start
|
51
|
+
@actors[actor] = member
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Terminate the group
|
56
|
+
def finalize
|
57
|
+
@actors.each do |actor, _|
|
58
|
+
begin
|
59
|
+
actor.terminate
|
60
|
+
rescue DeadActorError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Restart a crashed actor
|
66
|
+
def restart_actor(actor, reason)
|
67
|
+
member = @actors.delete actor
|
68
|
+
raise "a group member went missing. This shouldn't be!" unless member
|
69
|
+
|
70
|
+
# Ignore supervisors that shut down cleanly
|
71
|
+
return unless reason
|
72
|
+
|
73
|
+
actor = member.start
|
74
|
+
@actors[actor] = member
|
75
|
+
end
|
76
|
+
|
77
|
+
# A member of the group
|
78
|
+
class Member
|
79
|
+
def initialize(klass, options = {})
|
80
|
+
@klass = klass
|
81
|
+
|
82
|
+
# Stringify keys :/
|
83
|
+
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
84
|
+
|
85
|
+
@name = options['as']
|
86
|
+
@args = options['args'] ? Array(options['args']) : []
|
87
|
+
@method = options['method'] || 'new_link'
|
88
|
+
end
|
89
|
+
|
90
|
+
def start
|
91
|
+
actor = @klass.send(@method, *@args)
|
92
|
+
Actor[@name] = actor if @name
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Legacy support for the old name
|
98
|
+
Group = SupervisionGroup
|
99
|
+
end
|
data/lib/celluloid/supervisor.rb
CHANGED
@@ -8,12 +8,17 @@ module Celluloid
|
|
8
8
|
# Retrieve the actor this supervisor is supervising
|
9
9
|
attr_reader :actor
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
class << self
|
12
|
+
# Define the root of the supervision tree
|
13
|
+
attr_accessor :root
|
14
|
+
|
15
|
+
def supervise(klass, *args, &block)
|
16
|
+
new(nil, klass, *args, &block)
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
19
|
+
def supervise_as(name, klass, *args, &block)
|
20
|
+
new(name, klass, *args, &block)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
def initialize(name, klass, *args, &block)
|
@@ -23,6 +28,10 @@ module Celluloid
|
|
23
28
|
start_actor
|
24
29
|
end
|
25
30
|
|
31
|
+
def finalize
|
32
|
+
@actor.terminate if @actor and @actor.alive?
|
33
|
+
end
|
34
|
+
|
26
35
|
def start_actor(start_attempts = 3, sleep_interval = 30)
|
27
36
|
failures = 0
|
28
37
|
|
data/lib/celluloid/task.rb
CHANGED
@@ -46,6 +46,7 @@ module Celluloid
|
|
46
46
|
rescue TerminatedError
|
47
47
|
# Task was explicitly terminated
|
48
48
|
ensure
|
49
|
+
@status = :dead
|
49
50
|
actor.tasks.delete self
|
50
51
|
end
|
51
52
|
end
|
@@ -57,10 +58,6 @@ module Celluloid
|
|
57
58
|
nil
|
58
59
|
rescue FiberError
|
59
60
|
raise DeadTaskError, "cannot resume a dead task"
|
60
|
-
rescue RuntimeError => ex
|
61
|
-
# These occur spuriously on 1.9.3 if we shut down an actor with running tasks
|
62
|
-
return if ex.message == ""
|
63
|
-
raise
|
64
61
|
end
|
65
62
|
|
66
63
|
# Terminate this task
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# An abstraction around threads from the InternalPool which ensures we don't
|
3
|
+
# accidentally do things to threads which have been returned to the pool,
|
4
|
+
# such as, say, killing them
|
5
|
+
class ThreadHandle
|
6
|
+
def initialize
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@join = ConditionVariable.new
|
9
|
+
|
10
|
+
@thread = InternalPool.get do
|
11
|
+
begin
|
12
|
+
yield
|
13
|
+
ensure
|
14
|
+
@mutex.synchronize do
|
15
|
+
@thread = nil
|
16
|
+
@join.broadcast
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Is the thread running?
|
23
|
+
def alive?
|
24
|
+
@mutex.synchronize { @thread.alive? if @thread }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Forcibly kill the thread
|
28
|
+
def kill
|
29
|
+
!!@mutex.synchronize { @thread.kill if @thread }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Join to a running thread, blocking until it terminates
|
33
|
+
def join
|
34
|
+
@mutex.synchronize { @join.wait(@mutex) if @thread }
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/celluloid/version.rb
CHANGED
@@ -39,6 +39,10 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
39
39
|
abort example_crash
|
40
40
|
end
|
41
41
|
|
42
|
+
def crash_with_abort_raw(reason)
|
43
|
+
abort reason
|
44
|
+
end
|
45
|
+
|
42
46
|
def internal_hello
|
43
47
|
external_hello
|
44
48
|
end
|
@@ -59,6 +63,16 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
59
63
|
super || delegates?(method_name)
|
60
64
|
end
|
61
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
|
+
|
62
76
|
private
|
63
77
|
|
64
78
|
def delegates?(method_name)
|
@@ -132,25 +146,44 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
132
146
|
end.to raise_exception(ExampleCrash)
|
133
147
|
end
|
134
148
|
|
135
|
-
|
136
|
-
|
149
|
+
describe "when #abort is called" do
|
150
|
+
it "raises exceptions in the caller but keeps running" do
|
151
|
+
actor = actor_class.new "Al Pacino"
|
137
152
|
|
138
|
-
|
139
|
-
|
153
|
+
e = nil
|
154
|
+
line_no = nil
|
140
155
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
156
|
+
expect do
|
157
|
+
begin
|
158
|
+
line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
|
159
|
+
rescue => ex
|
160
|
+
e = ex
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
end.to raise_exception(ExampleCrash, "You die motherfucker!")
|
164
|
+
|
165
|
+
e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
|
166
|
+
e.foo.should be == :bar # Check the exception maintains instance variables
|
167
|
+
|
168
|
+
actor.should be_alive
|
169
|
+
end
|
149
170
|
|
150
|
-
|
151
|
-
|
171
|
+
it "converts strings to runtime errors" do
|
172
|
+
actor = actor_class.new "Al Pacino"
|
173
|
+
expect do
|
174
|
+
actor.crash_with_abort_raw "foo"
|
175
|
+
end.to raise_exception(RuntimeError, "foo")
|
176
|
+
end
|
152
177
|
|
153
|
-
|
178
|
+
it "crashes the caller if we pass neither String nor Exception" do
|
179
|
+
actor = actor_class.new "Al Pacino"
|
180
|
+
expect do
|
181
|
+
actor.crash_with_abort_raw 10
|
182
|
+
end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
|
183
|
+
|
184
|
+
actor.greet rescue nil # Ensure our actor has died.
|
185
|
+
actor.should_not be_alive
|
186
|
+
end
|
154
187
|
end
|
155
188
|
|
156
189
|
it "raises DeadActorError if methods are synchronously called on a dead actor" do
|
@@ -168,12 +201,24 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
168
201
|
actor.greet.should == "Hi, I'm Charlie Sheen"
|
169
202
|
end
|
170
203
|
|
204
|
+
it "handles asynchronous calls via #async" do
|
205
|
+
actor = actor_class.new "Troy McClure"
|
206
|
+
actor.async :change_name, "Charlie Sheen"
|
207
|
+
actor.greet.should == "Hi, I'm Charlie Sheen"
|
208
|
+
end
|
209
|
+
|
171
210
|
it "handles asynchronous calls to itself" do
|
172
211
|
actor = actor_class.new "Troy McClure"
|
173
212
|
actor.change_name_async "Charlie Sheen"
|
174
213
|
actor.greet.should == "Hi, I'm Charlie Sheen"
|
175
214
|
end
|
176
215
|
|
216
|
+
it "allows an actor to call private methods asynchronously with a bang" do
|
217
|
+
actor = actor_class.new "Troy McClure"
|
218
|
+
actor.call_private
|
219
|
+
actor.private_called.should be_true
|
220
|
+
end
|
221
|
+
|
177
222
|
it "knows if it's inside actor scope" do
|
178
223
|
Celluloid.should_not be_actor
|
179
224
|
actor = actor_class.new "Troy McClure"
|
@@ -224,7 +269,15 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
224
269
|
actor = actor_class.new "Arnold Schwarzenegger"
|
225
270
|
actor.should be_alive
|
226
271
|
actor.terminate
|
227
|
-
|
272
|
+
actor.join
|
273
|
+
actor.should_not be_alive
|
274
|
+
end
|
275
|
+
|
276
|
+
it "kills" do
|
277
|
+
actor = actor_class.new "Woody Harrelson"
|
278
|
+
actor.should be_alive
|
279
|
+
actor.kill
|
280
|
+
actor.join
|
228
281
|
actor.should_not be_alive
|
229
282
|
end
|
230
283
|
|
@@ -388,7 +441,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
388
441
|
received_obj.should == message
|
389
442
|
end
|
390
443
|
|
391
|
-
it "times out after the given interval" do
|
444
|
+
it "times out after the given interval", :pending => ENV['CI'] do
|
392
445
|
interval = 0.1
|
393
446
|
started_at = Time.now
|
394
447
|
|
@@ -520,10 +573,8 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
520
573
|
it "knows which tasks are waiting on calls to other actors" do
|
521
574
|
actor = @klass.new
|
522
575
|
|
523
|
-
# an alias for Celluloid::Actor#waiting_tasks
|
524
576
|
tasks = actor.tasks
|
525
577
|
tasks.size.should == 1
|
526
|
-
tasks.first.status.should == :running
|
527
578
|
|
528
579
|
future = actor.future(:blocking_call)
|
529
580
|
sleep 0.1 # hax! waiting for ^^^ call to actually start
|
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.
|
4
|
+
version: 0.11.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70191377653480 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70191377653480
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70191377602840 !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: *70191377602840
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: guard-rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70191350440520 !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: *70191350440520
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: benchmark_suite
|
49
|
-
requirement: &
|
49
|
+
requirement: &70191350401720 !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: *70191350401720
|
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:
|
@@ -73,19 +73,20 @@ files:
|
|
73
73
|
- lib/celluloid/fiber.rb
|
74
74
|
- lib/celluloid/fsm.rb
|
75
75
|
- lib/celluloid/future.rb
|
76
|
-
- lib/celluloid/
|
76
|
+
- lib/celluloid/internal_pool.rb
|
77
77
|
- lib/celluloid/links.rb
|
78
78
|
- lib/celluloid/logger.rb
|
79
79
|
- lib/celluloid/mailbox.rb
|
80
|
-
- lib/celluloid/
|
80
|
+
- lib/celluloid/pool_manager.rb
|
81
81
|
- lib/celluloid/receivers.rb
|
82
82
|
- lib/celluloid/registry.rb
|
83
83
|
- lib/celluloid/responses.rb
|
84
84
|
- lib/celluloid/rspec.rb
|
85
85
|
- lib/celluloid/signals.rb
|
86
|
+
- lib/celluloid/supervision_group.rb
|
86
87
|
- lib/celluloid/supervisor.rb
|
87
88
|
- lib/celluloid/task.rb
|
88
|
-
- lib/celluloid/
|
89
|
+
- lib/celluloid/thread_handle.rb
|
89
90
|
- lib/celluloid/timers.rb
|
90
91
|
- lib/celluloid/uuid.rb
|
91
92
|
- lib/celluloid/version.rb
|
@@ -113,8 +114,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
114
|
version: 1.3.6
|
114
115
|
requirements: []
|
115
116
|
rubyforge_project:
|
116
|
-
rubygems_version: 1.8.
|
117
|
+
rubygems_version: 1.8.10
|
117
118
|
signing_key:
|
118
119
|
specification_version: 3
|
119
120
|
summary: Actor-based concurrent object framework for Ruby
|
120
121
|
test_files: []
|
122
|
+
has_rdoc:
|
data/lib/celluloid/group.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
module Celluloid
|
2
|
-
# Supervise collections of actors as a group
|
3
|
-
class Group
|
4
|
-
include Celluloid
|
5
|
-
trap_exit :restart_supervisor
|
6
|
-
|
7
|
-
class << self
|
8
|
-
# Actors or sub-applications to be supervised
|
9
|
-
def supervisables
|
10
|
-
@supervisables ||= []
|
11
|
-
end
|
12
|
-
|
13
|
-
# Start this application (and watch it with a supervisor)
|
14
|
-
alias_method :run!, :supervise
|
15
|
-
|
16
|
-
# Run the application in the foreground with a simple watchdog
|
17
|
-
def run
|
18
|
-
loop do
|
19
|
-
supervisor = run!
|
20
|
-
|
21
|
-
# Take five, toplevel supervisor
|
22
|
-
sleep 5 while supervisor.alive?
|
23
|
-
|
24
|
-
Logger.error "!!! Celluloid::Application #{self} crashed. Restarting..."
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Register an actor class or a sub-application class to be launched and
|
29
|
-
# supervised while this application is running. Available options are:
|
30
|
-
#
|
31
|
-
# * as: register this application in the Celluloid::Actor[] directory
|
32
|
-
# * args: start the actor with the given arguments
|
33
|
-
def supervise(klass, options = {})
|
34
|
-
supervisables << Supervisable.new(klass, options)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Start the application
|
39
|
-
def initialize
|
40
|
-
@supervisors = {}
|
41
|
-
|
42
|
-
# This is some serious lolcode, but like... start the supervisors for
|
43
|
-
# this application
|
44
|
-
self.class.supervisables.each do |supervisable|
|
45
|
-
supervisor = supervisable.supervise
|
46
|
-
@supervisors[supervisor] = supervisable
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Restart a crashed supervisor
|
51
|
-
def restart_supervisor(supervisor, reason)
|
52
|
-
supervisable = @supervisors.delete supervisor
|
53
|
-
raise "a supervisable went missing. This shouldn't be!" unless supervisable
|
54
|
-
|
55
|
-
# Ignore supervisors that shut down cleanly
|
56
|
-
return unless reason
|
57
|
-
|
58
|
-
supervisor = supervisable.supervise
|
59
|
-
@supervisors[supervisor] = supervisable
|
60
|
-
end
|
61
|
-
|
62
|
-
# A subcomponent of an application to be supervised
|
63
|
-
class Supervisable
|
64
|
-
attr_reader :klass, :as, :args
|
65
|
-
|
66
|
-
def initialize(klass, options = {})
|
67
|
-
@klass = klass
|
68
|
-
|
69
|
-
# Stringify keys :/
|
70
|
-
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
71
|
-
|
72
|
-
@as = options['as']
|
73
|
-
@args = options['args'] || []
|
74
|
-
raise ":args should be an Array" unless @args.kind_of? Array
|
75
|
-
end
|
76
|
-
|
77
|
-
def supervise
|
78
|
-
Supervisor.new_link(@as, @klass, *@args)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Legacy support for the old name. Going away soon!
|
84
|
-
Application = Group
|
85
|
-
end
|
data/lib/celluloid/pool.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
module Celluloid
|
2
|
-
# Pools provide groups of actors which can service requests
|
3
|
-
class Pool
|
4
|
-
include Celluloid
|
5
|
-
trap_exit :crash_handler
|
6
|
-
attr_reader :max_actors
|
7
|
-
|
8
|
-
# Takes a class of actor to pool and a hash of options:
|
9
|
-
#
|
10
|
-
# * initial_size: how many actors to eagerly create
|
11
|
-
# * max_size: maximum number of actors (default one actor per CPU core)
|
12
|
-
# * args: an array of arguments to pass to the actor's initialize
|
13
|
-
def initialize(klass, options = {})
|
14
|
-
raise ArgumentError, "A Pool has a minimum size of 2" if options[:max_size] && options[:max_size] < 2
|
15
|
-
opts = {
|
16
|
-
:initial_size => 1,
|
17
|
-
:max_size => [Celluloid.cores, 2].max,
|
18
|
-
:args => []
|
19
|
-
}.merge(options)
|
20
|
-
|
21
|
-
@klass, @args = klass, opts[:args]
|
22
|
-
@max_actors = opts[:max_size]
|
23
|
-
@idle_actors, @running_actors = 0, 0
|
24
|
-
@actors = []
|
25
|
-
|
26
|
-
opts[:initial_size].times do
|
27
|
-
@actors << spawn
|
28
|
-
@idle_actors += 1
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Get an actor from the pool. Actors taken from the pool must be put back
|
33
|
-
# with Pool#put. Alternatively, you can use get with a block form:
|
34
|
-
#
|
35
|
-
# pool.get { |actor| ... }
|
36
|
-
#
|
37
|
-
# This will automatically return actors to the pool when the block completes
|
38
|
-
def get
|
39
|
-
if @max_actors and @running_actors == @max_actors
|
40
|
-
wait :ready
|
41
|
-
end
|
42
|
-
|
43
|
-
actor = @actors.pop
|
44
|
-
if actor
|
45
|
-
@idle_actors -= 1
|
46
|
-
else
|
47
|
-
actor = spawn
|
48
|
-
end
|
49
|
-
|
50
|
-
if block_given?
|
51
|
-
begin
|
52
|
-
yield actor
|
53
|
-
rescue => ex
|
54
|
-
end
|
55
|
-
|
56
|
-
put actor
|
57
|
-
abort ex if ex
|
58
|
-
nil
|
59
|
-
else
|
60
|
-
actor
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Return an actor to the pool
|
65
|
-
def put(actor)
|
66
|
-
begin
|
67
|
-
raise TypeError, "expecting a #{@klass} actor" unless actor.is_a? @klass
|
68
|
-
rescue DeadActorError
|
69
|
-
# The actor may have died before it was handed back to us
|
70
|
-
# We'll let the crash_handler deal with it in due time
|
71
|
-
return
|
72
|
-
end
|
73
|
-
|
74
|
-
@actors << actor
|
75
|
-
@idle_actors += 1
|
76
|
-
end
|
77
|
-
|
78
|
-
# Number of active actors in this pool
|
79
|
-
def size
|
80
|
-
@running_actors
|
81
|
-
end
|
82
|
-
|
83
|
-
# Number of idle actors in the pool
|
84
|
-
def idle_count
|
85
|
-
@idle_actors
|
86
|
-
end
|
87
|
-
alias_method :idle_size, :idle_count
|
88
|
-
|
89
|
-
# Handle crashed actors
|
90
|
-
def crash_handler(actor, reason)
|
91
|
-
@idle_actors -= 1 if @actors.delete actor
|
92
|
-
@running_actors -= 1
|
93
|
-
|
94
|
-
# If we were maxed out before...
|
95
|
-
signal :ready if @max_actors and @running_actors + 1 == @max_actors
|
96
|
-
end
|
97
|
-
|
98
|
-
# Spawn an actor of the given class
|
99
|
-
def spawn
|
100
|
-
worker = @klass.new_link(*@args)
|
101
|
-
@running_actors += 1
|
102
|
-
worker
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|