celluloid 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +23 -6
- data/lib/celluloid/actor.rb +146 -56
- data/lib/celluloid/boot.rb +9 -0
- data/lib/celluloid/calls.rb +3 -3
- data/lib/celluloid/core_ext.rb +1 -6
- data/lib/celluloid/cpu_counter.rb +2 -0
- data/lib/celluloid/future.rb +5 -0
- data/lib/celluloid/links.rb +1 -3
- data/lib/celluloid/mailbox.rb +14 -20
- data/lib/celluloid/method.rb +19 -0
- data/lib/celluloid/notifications.rb +16 -17
- data/lib/celluloid/pool_manager.rb +40 -11
- data/lib/celluloid/proxies/abstract_proxy.rb +17 -0
- data/lib/celluloid/{actor_proxy.rb → proxies/actor_proxy.rb} +38 -39
- data/lib/celluloid/proxies/async_proxy.rb +19 -0
- data/lib/celluloid/proxies/future_proxy.rb +19 -0
- data/lib/celluloid/registry.rb +8 -5
- data/lib/celluloid/rspec.rb +6 -0
- data/lib/celluloid/supervision_group.rb +23 -10
- data/lib/celluloid/system_events.rb +54 -0
- data/lib/celluloid/task.rb +7 -63
- data/lib/celluloid/tasks/task_fiber.rb +65 -0
- data/lib/celluloid/tasks/task_thread.rb +78 -0
- data/lib/celluloid/thread_handle.rb +2 -1
- data/lib/celluloid/version.rb +1 -1
- data/lib/celluloid.rb +149 -114
- data/spec/support/actor_examples.rb +182 -26
- data/spec/support/example_actor_class.rb +6 -2
- data/spec/support/mailbox_examples.rb +3 -18
- data/spec/support/task_examples.rb +36 -0
- metadata +21 -13
- data/lib/celluloid/events.rb +0 -26
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
=========
|
|
3
3
|
[](http://travis-ci.org/celluloid/celluloid)
|
|
4
4
|
[](https://gemnasium.com/celluloid/celluloid)
|
|
@@ -17,7 +17,7 @@ Much of the difficulty with building concurrent programs in Ruby arises because
|
|
|
17
17
|
the object-oriented mechanisms for structuring code, such as classes and
|
|
18
18
|
inheritance, are separate from the concurrency mechanisms, such as threads and
|
|
19
19
|
locks. Celluloid combines these into a single structure, an active object
|
|
20
|
-
running within a thread, called an "actor".
|
|
20
|
+
running within a thread, called an "actor", or in Celluloid vernacular, a "cell".
|
|
21
21
|
|
|
22
22
|
By combining concurrency with object oriented programming, Celluloid frees you
|
|
23
23
|
up from worry about where to use threads and locks. Celluloid combines them
|
|
@@ -72,15 +72,32 @@ for more detailed documentation and usage notes.
|
|
|
72
72
|
Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
|
73
73
|
or visit us on IRC at #celluloid on freenode
|
|
74
74
|
|
|
75
|
-
### Is it any good?
|
|
76
|
-
|
|
77
|
-
[Yes.](http://news.ycombinator.com/item?id=3067434)
|
|
78
|
-
|
|
79
75
|
### Is It "Production Ready™"?
|
|
80
76
|
|
|
81
77
|
Yes, many users are now running Celluloid in production by using
|
|
82
78
|
[Sidekiq](https://github.com/mperham/sidekiq)
|
|
83
79
|
|
|
80
|
+
Installation
|
|
81
|
+
------------
|
|
82
|
+
|
|
83
|
+
Add this line to your application's Gemfile:
|
|
84
|
+
|
|
85
|
+
gem 'celluloid'
|
|
86
|
+
|
|
87
|
+
And then execute:
|
|
88
|
+
|
|
89
|
+
$ bundle
|
|
90
|
+
|
|
91
|
+
Or install it yourself as:
|
|
92
|
+
|
|
93
|
+
$ gem install celluloid
|
|
94
|
+
|
|
95
|
+
Inside of your Ruby program do:
|
|
96
|
+
|
|
97
|
+
require 'celluloid'
|
|
98
|
+
|
|
99
|
+
...to pull it in as a dependency.
|
|
100
|
+
|
|
84
101
|
Supported Platforms
|
|
85
102
|
-------------------
|
|
86
103
|
|
data/lib/celluloid/actor.rb
CHANGED
|
@@ -7,6 +7,9 @@ module Celluloid
|
|
|
7
7
|
# Trying to do something to a dead actor
|
|
8
8
|
class DeadActorError < StandardError; end
|
|
9
9
|
|
|
10
|
+
# A timeout occured before the given request could complete
|
|
11
|
+
class TimeoutError < StandardError; end
|
|
12
|
+
|
|
10
13
|
# The caller made an error, not the current actor
|
|
11
14
|
class AbortError < StandardError
|
|
12
15
|
attr_reader :cause
|
|
@@ -17,6 +20,8 @@ module Celluloid
|
|
|
17
20
|
end
|
|
18
21
|
end
|
|
19
22
|
|
|
23
|
+
LINKING_TIMEOUT = 5 # linking times out after 5 seconds
|
|
24
|
+
|
|
20
25
|
# Actors are Celluloid's concurrency primitive. They're implemented as
|
|
21
26
|
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
|
22
27
|
# messages.
|
|
@@ -64,9 +69,13 @@ module Celluloid
|
|
|
64
69
|
# The current task will be automatically resumed when we get a response
|
|
65
70
|
Task.suspend(:callwait).value
|
|
66
71
|
else
|
|
67
|
-
# Otherwise we're inside a normal thread, so block
|
|
68
|
-
response =
|
|
69
|
-
|
|
72
|
+
# Otherwise we're inside a normal thread or exclusive, so block
|
|
73
|
+
response = loop do
|
|
74
|
+
message = Thread.mailbox.receive do |msg|
|
|
75
|
+
msg.respond_to?(:call) and msg.call == call
|
|
76
|
+
end
|
|
77
|
+
break message unless message.is_a?(SystemEvent)
|
|
78
|
+
Thread.current[:actor].handle_system_event(message)
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
response.value
|
|
@@ -97,18 +106,68 @@ module Celluloid
|
|
|
97
106
|
actors = []
|
|
98
107
|
Thread.list.each do |t|
|
|
99
108
|
actor = t[:actor]
|
|
100
|
-
actors << actor.proxy if actor
|
|
109
|
+
actors << actor.proxy if actor and actor.respond_to?(:proxy)
|
|
101
110
|
end
|
|
102
111
|
actors
|
|
103
112
|
end
|
|
113
|
+
|
|
114
|
+
# Watch for exit events from another actor
|
|
115
|
+
def monitor(actor)
|
|
116
|
+
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
|
117
|
+
Thread.current[:actor].linking_request(actor, :link)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Stop waiting for exit events from another actor
|
|
121
|
+
def unmonitor(actor)
|
|
122
|
+
raise NotActorError, "can't link outside actor context" unless Celluloid.actor?
|
|
123
|
+
Thread.current[:actor].linking_request(actor, :unlink)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Link to another actor
|
|
127
|
+
def link(actor)
|
|
128
|
+
monitor actor
|
|
129
|
+
Thread.current[:actor].links << actor
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Unlink from another actor
|
|
133
|
+
def unlink(actor)
|
|
134
|
+
unmonitor actor
|
|
135
|
+
Thread.current[:actor].links.delete actor
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Are we monitoring the given actor?
|
|
139
|
+
def monitoring?(actor)
|
|
140
|
+
actor.links.include? Actor.current
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Are we bidirectionally linked to the given actor?
|
|
144
|
+
def linked_to?(actor)
|
|
145
|
+
monitoring?(actor) && Thread.current[:actor].links.include?(actor)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Forcibly kill a given actor
|
|
149
|
+
def kill(actor)
|
|
150
|
+
actor.thread.kill
|
|
151
|
+
begin
|
|
152
|
+
actor.mailbox.shutdown
|
|
153
|
+
rescue DeadActorError
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Wait for an actor to terminate
|
|
158
|
+
def join(actor)
|
|
159
|
+
actor.thread.join
|
|
160
|
+
actor
|
|
161
|
+
end
|
|
104
162
|
end
|
|
105
163
|
|
|
106
164
|
# Wrap the given subject with an Actor
|
|
107
|
-
def initialize(subject)
|
|
165
|
+
def initialize(subject, options = {})
|
|
108
166
|
@subject = subject
|
|
109
|
-
@mailbox =
|
|
110
|
-
@exit_handler =
|
|
111
|
-
@exclusives =
|
|
167
|
+
@mailbox = options[:mailbox] || Mailbox.new
|
|
168
|
+
@exit_handler = options[:exit_handler]
|
|
169
|
+
@exclusives = options[:exclusive_methods]
|
|
170
|
+
@task_class = options[:task_class] || Celluloid.task_class
|
|
112
171
|
|
|
113
172
|
@tasks = Set.new
|
|
114
173
|
@links = Links.new
|
|
@@ -128,6 +187,33 @@ module Celluloid
|
|
|
128
187
|
@proxy = ActorProxy.new(self)
|
|
129
188
|
end
|
|
130
189
|
|
|
190
|
+
# Run the actor loop
|
|
191
|
+
def run
|
|
192
|
+
begin
|
|
193
|
+
while @running
|
|
194
|
+
if message = @mailbox.receive(timeout_interval)
|
|
195
|
+
handle_message message
|
|
196
|
+
else
|
|
197
|
+
# No message indicates a timeout
|
|
198
|
+
@timers.fire
|
|
199
|
+
@receivers.fire_timers
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
rescue MailboxShutdown
|
|
203
|
+
# If the mailbox detects shutdown, exit the actor
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
shutdown
|
|
207
|
+
rescue Exception => ex
|
|
208
|
+
handle_crash(ex)
|
|
209
|
+
raise unless ex.is_a? StandardError
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Terminate this actor
|
|
213
|
+
def terminate
|
|
214
|
+
@running = false
|
|
215
|
+
end
|
|
216
|
+
|
|
131
217
|
# Is this actor running in exclusive mode?
|
|
132
218
|
def exclusive?
|
|
133
219
|
@exclusive
|
|
@@ -141,9 +227,33 @@ module Celluloid
|
|
|
141
227
|
@exclusive = false
|
|
142
228
|
end
|
|
143
229
|
|
|
144
|
-
#
|
|
145
|
-
def
|
|
146
|
-
|
|
230
|
+
# Perform a linking request with another actor
|
|
231
|
+
def linking_request(receiver, type)
|
|
232
|
+
exclusive do
|
|
233
|
+
start_time = Time.now
|
|
234
|
+
|
|
235
|
+
receiver.mailbox << LinkingRequest.new(Actor.current, type)
|
|
236
|
+
system_events = []
|
|
237
|
+
loop do
|
|
238
|
+
wait_interval = start_time + LINKING_TIMEOUT - Time.now
|
|
239
|
+
message = @mailbox.receive(wait_interval) do |msg|
|
|
240
|
+
msg.is_a?(LinkingResponse) && msg.actor == receiver && msg.type == type
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
case message
|
|
244
|
+
when LinkingResponse
|
|
245
|
+
# We're done!
|
|
246
|
+
system_events.each { |ev| handle_system_event(ev) }
|
|
247
|
+
return
|
|
248
|
+
when NilClass
|
|
249
|
+
raise TimeoutError, "linking timeout of #{LINKING_TIMEOUT} seconds exceeded"
|
|
250
|
+
when SystemEvent
|
|
251
|
+
# Queue up pending system events to be processed after we've successfully linked
|
|
252
|
+
system_events << message
|
|
253
|
+
else raise 'wtf'
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
147
257
|
end
|
|
148
258
|
|
|
149
259
|
# Send a signal with the given name to all waiting methods
|
|
@@ -158,41 +268,16 @@ module Celluloid
|
|
|
158
268
|
|
|
159
269
|
# Receive an asynchronous message
|
|
160
270
|
def receive(timeout = nil, &block)
|
|
161
|
-
|
|
162
|
-
@receivers.receive(timeout, &block)
|
|
163
|
-
|
|
164
|
-
handle_system_event(event)
|
|
165
|
-
retry
|
|
166
|
-
end
|
|
167
|
-
end
|
|
271
|
+
loop do
|
|
272
|
+
message = @receivers.receive(timeout, &block)
|
|
273
|
+
break message unless message.is_a?(SystemEvent)
|
|
168
274
|
|
|
169
|
-
|
|
170
|
-
def run
|
|
171
|
-
begin
|
|
172
|
-
while @running
|
|
173
|
-
if message = @mailbox.receive(timeout)
|
|
174
|
-
handle_message message
|
|
175
|
-
else
|
|
176
|
-
# No message indicates a timeout
|
|
177
|
-
@timers.fire
|
|
178
|
-
@receivers.fire_timers
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
rescue SystemEvent => event
|
|
182
|
-
handle_system_event event
|
|
183
|
-
retry
|
|
184
|
-
rescue MailboxShutdown
|
|
185
|
-
# If the mailbox detects shutdown, exit the actor
|
|
275
|
+
handle_system_event(message)
|
|
186
276
|
end
|
|
187
|
-
|
|
188
|
-
shutdown
|
|
189
|
-
rescue Exception => ex
|
|
190
|
-
handle_crash(ex)
|
|
191
|
-
raise unless ex.is_a? StandardError
|
|
192
277
|
end
|
|
193
278
|
|
|
194
279
|
# How long to wait until the next timer fires
|
|
195
|
-
def
|
|
280
|
+
def timeout_interval
|
|
196
281
|
i1 = @timers.wait_interval
|
|
197
282
|
i2 = @receivers.wait_interval
|
|
198
283
|
|
|
@@ -206,17 +291,13 @@ module Celluloid
|
|
|
206
291
|
end
|
|
207
292
|
|
|
208
293
|
# Schedule a block to run at the given time
|
|
209
|
-
def after(interval)
|
|
210
|
-
@timers.after(interval)
|
|
211
|
-
Task.new(:timer) { yield }.resume
|
|
212
|
-
end
|
|
294
|
+
def after(interval, &block)
|
|
295
|
+
@timers.after(interval) { task(:timer, &block) }
|
|
213
296
|
end
|
|
214
297
|
|
|
215
298
|
# Schedule a block to run at the given time
|
|
216
|
-
def every(interval)
|
|
217
|
-
@timers.every(interval)
|
|
218
|
-
Task.new(:timer) { yield }.resume
|
|
219
|
-
end
|
|
299
|
+
def every(interval, &block)
|
|
300
|
+
@timers.every(interval) { task(:timer, &block) }
|
|
220
301
|
end
|
|
221
302
|
|
|
222
303
|
# Sleep for the given amount of time
|
|
@@ -233,12 +314,10 @@ module Celluloid
|
|
|
233
314
|
# Handle standard low-priority messages
|
|
234
315
|
def handle_message(message)
|
|
235
316
|
case message
|
|
317
|
+
when SystemEvent
|
|
318
|
+
handle_system_event message
|
|
236
319
|
when Call
|
|
237
|
-
|
|
238
|
-
exclusive { message.dispatch(@subject) }
|
|
239
|
-
else
|
|
240
|
-
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
|
241
|
-
end
|
|
320
|
+
task(:message_handler, message.method) { message.dispatch(@subject) }
|
|
242
321
|
when Response
|
|
243
322
|
message.dispatch
|
|
244
323
|
else
|
|
@@ -251,7 +330,9 @@ module Celluloid
|
|
|
251
330
|
def handle_system_event(event)
|
|
252
331
|
case event
|
|
253
332
|
when ExitEvent
|
|
254
|
-
|
|
333
|
+
task(:exit_handler, @exit_handler) { handle_exit_event event }
|
|
334
|
+
when LinkingRequest
|
|
335
|
+
event.process(links)
|
|
255
336
|
when NamingRequest
|
|
256
337
|
@name = event.name
|
|
257
338
|
when TerminationRequest
|
|
@@ -289,7 +370,7 @@ module Celluloid
|
|
|
289
370
|
# Run the user-defined finalizer, if one is set
|
|
290
371
|
def run_finalizer
|
|
291
372
|
return unless @subject.respond_to? :finalize
|
|
292
|
-
|
|
373
|
+
task(:finalizer, :finalize) { @subject.finalize }
|
|
293
374
|
rescue => ex
|
|
294
375
|
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
|
295
376
|
end
|
|
@@ -302,5 +383,14 @@ module Celluloid
|
|
|
302
383
|
rescue => ex
|
|
303
384
|
Logger.crash("#{@subject.class}: CLEANUP CRASHED!", ex)
|
|
304
385
|
end
|
|
386
|
+
|
|
387
|
+
# Run a method inside a task unless it's exclusive
|
|
388
|
+
def task(task_type, method_name = nil, &block)
|
|
389
|
+
if @exclusives && (@exclusives == :all || @exclusives.include?(method_name))
|
|
390
|
+
exclusive { block.call }
|
|
391
|
+
else
|
|
392
|
+
@task_class.new(:message_handler, &block).resume
|
|
393
|
+
end
|
|
394
|
+
end
|
|
305
395
|
end
|
|
306
396
|
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Things to run after Celluloid is fully loaded
|
|
2
|
+
|
|
3
|
+
# Configure default systemwide settings
|
|
4
|
+
Celluloid.logger = Logger.new STDERR
|
|
5
|
+
Celluloid.task_class = Celluloid::TaskFiber
|
|
6
|
+
|
|
7
|
+
# Launch the notifications fanout actor
|
|
8
|
+
# FIXME: We should set up the supervision hierarchy here
|
|
9
|
+
Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
|
data/lib/celluloid/calls.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Celluloid
|
|
|
9
9
|
|
|
10
10
|
def check_signature(obj)
|
|
11
11
|
unless obj.respond_to? @method
|
|
12
|
-
raise NoMethodError, "undefined method `#{@method}' for #{obj.
|
|
12
|
+
raise NoMethodError, "undefined method `#{@method}' for #{obj.to_s}"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
begin
|
|
@@ -39,7 +39,7 @@ module Celluloid
|
|
|
39
39
|
class SyncCall < Call
|
|
40
40
|
attr_reader :caller, :task
|
|
41
41
|
|
|
42
|
-
def initialize(caller, method, arguments = [], block = nil, task =
|
|
42
|
+
def initialize(caller, method, arguments = [], block = nil, task = Thread.current[:task])
|
|
43
43
|
super(method, arguments, block)
|
|
44
44
|
@caller = caller
|
|
45
45
|
@task = task
|
|
@@ -99,7 +99,7 @@ module Celluloid
|
|
|
99
99
|
obj.send(@method, *@arguments, &@block)
|
|
100
100
|
rescue AbortError => ex
|
|
101
101
|
# Swallow aborted async calls, as they indicate the caller made a mistake
|
|
102
|
-
Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex)
|
|
102
|
+
Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex.cause)
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
end
|
data/lib/celluloid/core_ext.rb
CHANGED
data/lib/celluloid/future.rb
CHANGED
data/lib/celluloid/links.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require 'thread'
|
|
2
|
-
|
|
3
1
|
module Celluloid
|
|
4
2
|
# Linked actors send each other system events
|
|
5
3
|
class Links
|
|
@@ -31,7 +29,7 @@ module Celluloid
|
|
|
31
29
|
|
|
32
30
|
# Send an event message to all actors
|
|
33
31
|
def send_event(event)
|
|
34
|
-
each { |actor| actor.mailbox
|
|
32
|
+
each { |actor| actor.mailbox << event }
|
|
35
33
|
end
|
|
36
34
|
|
|
37
35
|
# Generate a string representation
|
data/lib/celluloid/mailbox.rb
CHANGED
|
@@ -13,9 +13,9 @@ module Celluloid
|
|
|
13
13
|
alias_method :address, :object_id
|
|
14
14
|
|
|
15
15
|
def initialize
|
|
16
|
-
@messages
|
|
17
|
-
@mutex
|
|
18
|
-
@dead
|
|
16
|
+
@messages = []
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
@dead = false
|
|
19
19
|
@condition = ConditionVariable.new
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -23,9 +23,18 @@ module Celluloid
|
|
|
23
23
|
def <<(message)
|
|
24
24
|
@mutex.lock
|
|
25
25
|
begin
|
|
26
|
-
|
|
26
|
+
if message.is_a?(SystemEvent)
|
|
27
|
+
# Silently swallow system events sent to dead actors
|
|
28
|
+
return if @dead
|
|
29
|
+
|
|
30
|
+
# SystemEvents are high priority messages so they get added to the
|
|
31
|
+
# head of our message queue instead of the end
|
|
32
|
+
@messages.unshift message
|
|
33
|
+
else
|
|
34
|
+
raise MailboxError, "dead recipient" if @dead
|
|
35
|
+
@messages << message
|
|
36
|
+
end
|
|
27
37
|
|
|
28
|
-
@messages << message
|
|
29
38
|
@condition.signal
|
|
30
39
|
nil
|
|
31
40
|
ensure
|
|
@@ -33,20 +42,6 @@ module Celluloid
|
|
|
33
42
|
end
|
|
34
43
|
end
|
|
35
44
|
|
|
36
|
-
# Add a high-priority system event to the Mailbox
|
|
37
|
-
def system_event(event)
|
|
38
|
-
@mutex.lock
|
|
39
|
-
begin
|
|
40
|
-
unless @dead # Silently fail if messages are sent to dead actors
|
|
41
|
-
@messages.unshift event
|
|
42
|
-
@condition.signal
|
|
43
|
-
end
|
|
44
|
-
nil
|
|
45
|
-
ensure
|
|
46
|
-
@mutex.unlock rescue nil
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
45
|
# Receive a message from the Mailbox
|
|
51
46
|
def receive(timeout = nil, &block)
|
|
52
47
|
message = nil
|
|
@@ -92,7 +87,6 @@ module Celluloid
|
|
|
92
87
|
message = @messages.shift
|
|
93
88
|
end
|
|
94
89
|
|
|
95
|
-
raise message if message.is_a? SystemEvent
|
|
96
90
|
message
|
|
97
91
|
end
|
|
98
92
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Celluloid
|
|
2
|
+
# Method handles that route through an actor proxy
|
|
3
|
+
class Method
|
|
4
|
+
def initialize(actor, name)
|
|
5
|
+
raise NameError, "undefined method `#{name}'" unless actor.respond_to? name
|
|
6
|
+
|
|
7
|
+
@actor, @name = actor, name
|
|
8
|
+
@klass = @actor.class
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(*args, &block)
|
|
12
|
+
@actor._send_(@name, *args, &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def inspect
|
|
16
|
+
"#<Celluloid::Method #{@klass}#{@name}>"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
module Celluloid
|
|
2
2
|
module Notifications
|
|
3
|
+
def self.notifier
|
|
4
|
+
Actor[:notifications_fanout] or raise DeadActorError, "notifications fanout actor not running"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def publish(pattern, *args)
|
|
8
|
+
Celluloid::Notifications.notifier.publish(pattern, *args)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def subscribe(pattern, method)
|
|
12
|
+
Celluloid::Notifications.notifier.subscribe(Actor.current, pattern, method)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def unsubscribe(*args)
|
|
16
|
+
Celluloid::Notifications.notifier.unsubscribe(*args)
|
|
17
|
+
end
|
|
18
|
+
|
|
3
19
|
class Fanout
|
|
4
20
|
include Celluloid
|
|
5
21
|
trap_exit :prune
|
|
@@ -63,22 +79,5 @@ module Celluloid
|
|
|
63
79
|
@pattern && @pattern === subscriber_or_pattern
|
|
64
80
|
end
|
|
65
81
|
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
82
|
end
|
|
84
83
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
1
3
|
module Celluloid
|
|
2
4
|
# Manages a fixed-size pool of workers
|
|
3
5
|
# Delegates work (i.e. methods) and supervises workers
|
|
@@ -7,14 +9,28 @@ module Celluloid
|
|
|
7
9
|
trap_exit :crash_handler
|
|
8
10
|
|
|
9
11
|
def initialize(worker_class, options = {})
|
|
10
|
-
@size = options[:size]
|
|
11
|
-
raise ArgumentError, "minimum pool size is 2" if @size
|
|
12
|
+
@size = options[:size] || [Celluloid.cores, 2].max
|
|
13
|
+
raise ArgumentError, "minimum pool size is 2" if @size < 2
|
|
12
14
|
|
|
13
|
-
@
|
|
15
|
+
@worker_class = worker_class
|
|
14
16
|
@args = options[:args] ? Array(options[:args]) : []
|
|
15
17
|
|
|
16
|
-
@worker_class = worker_class
|
|
17
18
|
@idle = @size.times.map { worker_class.new_link(*@args) }
|
|
19
|
+
|
|
20
|
+
# FIXME: Another data structure (e.g. Set) would be more appropriate
|
|
21
|
+
# here except it causes MRI to crash :o
|
|
22
|
+
@busy = []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def finalize
|
|
26
|
+
terminators = (@idle + @busy).each do |actor|
|
|
27
|
+
begin
|
|
28
|
+
actor.future(:terminate)
|
|
29
|
+
rescue DeadActorError, MailboxError
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
terminators.compact.each { |terminator| terminator.value rescue nil }
|
|
18
34
|
end
|
|
19
35
|
|
|
20
36
|
def _send_(method, *args, &block)
|
|
@@ -22,10 +38,17 @@ module Celluloid
|
|
|
22
38
|
|
|
23
39
|
begin
|
|
24
40
|
worker._send_ method, *args, &block
|
|
41
|
+
rescue DeadActorError # if we get a dead actor out of the pool
|
|
42
|
+
wait :respawn_complete
|
|
43
|
+
worker = __provision_worker
|
|
44
|
+
retry
|
|
25
45
|
rescue Exception => ex
|
|
26
46
|
abort ex
|
|
27
47
|
ensure
|
|
28
|
-
|
|
48
|
+
if worker.alive?
|
|
49
|
+
@idle << worker
|
|
50
|
+
@busy.delete worker
|
|
51
|
+
end
|
|
29
52
|
end
|
|
30
53
|
end
|
|
31
54
|
|
|
@@ -56,23 +79,29 @@ module Celluloid
|
|
|
56
79
|
# Provision a new worker
|
|
57
80
|
def __provision_worker
|
|
58
81
|
while @idle.empty?
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
response = exclusive { receive { |msg| msg.is_a? Response } }
|
|
82
|
+
# Wait for responses from one of the busy workers
|
|
83
|
+
response = exclusive { receive { |msg| msg.is_a?(Response) } }
|
|
62
84
|
Thread.current[:actor].handle_message(response)
|
|
63
85
|
end
|
|
64
|
-
|
|
86
|
+
|
|
87
|
+
worker = @idle.shift
|
|
88
|
+
@busy << worker
|
|
89
|
+
|
|
90
|
+
worker
|
|
65
91
|
end
|
|
66
92
|
|
|
67
93
|
# Spawn a new worker for every crashed one
|
|
68
94
|
def crash_handler(actor, reason)
|
|
95
|
+
@busy.delete actor
|
|
69
96
|
@idle.delete actor
|
|
70
|
-
return unless reason
|
|
97
|
+
return unless reason
|
|
98
|
+
|
|
71
99
|
@idle << @worker_class.new_link(*@args)
|
|
100
|
+
signal :respawn_complete
|
|
72
101
|
end
|
|
73
102
|
|
|
74
103
|
def respond_to?(method)
|
|
75
|
-
super ||
|
|
104
|
+
super || @worker_class.instance_methods.include?(method.to_sym)
|
|
76
105
|
end
|
|
77
106
|
|
|
78
107
|
def method_missing(method, *args, &block)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Celluloid
|
|
2
|
+
# Base class of all Celluloid proxies
|
|
3
|
+
class AbstractProxy < BasicObject
|
|
4
|
+
# Needed for storing proxies in data structures
|
|
5
|
+
needed = [:object_id, :__id__, :hash] - instance_methods
|
|
6
|
+
if needed.any?
|
|
7
|
+
include ::Kernel.dup.module_eval {
|
|
8
|
+
undef_method *(instance_methods - needed)
|
|
9
|
+
self
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# rubinius bug? These methods disappear when we include hacked kernel
|
|
13
|
+
define_method :==, ::BasicObject.instance_method(:==) unless instance_methods.include?(:==)
|
|
14
|
+
alias_method(:equal?, :==) unless instance_methods.include?(:equal?)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|