celluloid 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +11 -4
- data/lib/celluloid.rb +63 -10
- data/lib/celluloid/actor.rb +108 -75
- data/lib/celluloid/actor_proxy.rb +8 -0
- data/lib/celluloid/future.rb +0 -2
- data/lib/celluloid/group.rb +3 -0
- data/lib/celluloid/logger.rb +24 -3
- data/lib/celluloid/mailbox.rb +46 -42
- data/lib/celluloid/pool.rb +103 -0
- data/lib/celluloid/receivers.rb +12 -8
- data/lib/celluloid/signals.rb +2 -1
- data/lib/celluloid/supervisor.rb +4 -1
- data/lib/celluloid/thread_pool.rb +11 -8
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +9 -0
- metadata +11 -10
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
Celluloid
|
1
|
+
![Celluloid](https://github.com/tarcieri/celluloid/raw/master/logo.png)
|
2
2
|
=========
|
3
|
-
[![Build Status](
|
3
|
+
[![Build Status](https://secure.travis-ci.org/tarcieri/celluloid.png?branch=master)](http://travis-ci.org/tarcieri/celluloid)
|
4
|
+
[![Dependency Status](https://gemnasium.com/tarcieri/celluloid.png)](https://gemnasium.com/tarcieri/celluloid)
|
4
5
|
|
5
6
|
> "I thought of objects being like biological cells and/or individual
|
6
7
|
> computers on a network, only able to communicate with messages"
|
@@ -137,12 +138,18 @@ and [linking](https://github.com/tarcieri/celluloid/wiki/linking)
|
|
137
138
|
[Please see the Celluloid Wiki](https://github.com/tarcieri/celluloid/wiki)
|
138
139
|
for additional usage information.
|
139
140
|
|
141
|
+
Suggested Reading
|
142
|
+
-----------------
|
143
|
+
|
144
|
+
* [Concurrent Object-Oriented Programming in Python with ATOM](http://python.org/workshops/1997-10/proceedings/atom/)
|
145
|
+
|
140
146
|
Contributing to Celluloid
|
141
147
|
-------------------------
|
142
148
|
|
143
|
-
* Fork
|
149
|
+
* Fork this repository on github
|
144
150
|
* Make your changes and send me a pull request
|
145
|
-
* If I like them I'll merge them
|
151
|
+
* If I like them I'll merge them
|
152
|
+
* If I've accepted a patch, feel free to ask for commit access
|
146
153
|
|
147
154
|
License
|
148
155
|
-------
|
data/lib/celluloid.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'thread'
|
3
|
+
require 'timeout'
|
3
4
|
|
4
5
|
module Celluloid
|
6
|
+
SHUTDOWN_TIMEOUT = 60 # How long actors have to terminate
|
5
7
|
@logger = Logger.new STDERR
|
6
8
|
|
7
9
|
class << self
|
@@ -16,6 +18,11 @@ module Celluloid
|
|
16
18
|
!!Thread.current[:actor]
|
17
19
|
end
|
18
20
|
|
21
|
+
# Is current actor running in exclusive mode?
|
22
|
+
def exclusive?
|
23
|
+
actor? and Thread.current[:actor].exclusive?
|
24
|
+
end
|
25
|
+
|
19
26
|
# Obtain the currently running actor (if one exists)
|
20
27
|
def current_actor
|
21
28
|
actor = Thread.current[:actor]
|
@@ -43,14 +50,43 @@ module Celluloid
|
|
43
50
|
Kernel.sleep interval
|
44
51
|
end
|
45
52
|
end
|
53
|
+
|
54
|
+
# Define an exception handler for actor crashes
|
55
|
+
def exception_handler(&block)
|
56
|
+
Logger.exception_handler(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Shut down all running actors
|
60
|
+
# FIXME: This should probably attempt a graceful shutdown of the supervision
|
61
|
+
# tree before iterating through all actors and telling them to terminate.
|
62
|
+
def shutdown
|
63
|
+
Timeout.timeout(SHUTDOWN_TIMEOUT) do
|
64
|
+
futures = Actor.all.each do |actor|
|
65
|
+
begin
|
66
|
+
actor.future(:terminate)
|
67
|
+
rescue DeadActorError, MailboxError
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
futures.each do |future|
|
72
|
+
begin
|
73
|
+
future.value
|
74
|
+
rescue DeadActorError, MailboxError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
46
79
|
end
|
47
80
|
|
81
|
+
# Terminate all actors at exit
|
82
|
+
at_exit { shutdown }
|
83
|
+
|
48
84
|
# Class methods added to classes which include Celluloid
|
49
85
|
module ClassMethods
|
50
86
|
# Create a new actor
|
51
87
|
def new(*args, &block)
|
52
88
|
proxy = Actor.new(allocate).proxy
|
53
|
-
proxy.send(:initialize, *args, &block)
|
89
|
+
proxy.send(:__send__, :initialize, *args, &block)
|
54
90
|
proxy
|
55
91
|
end
|
56
92
|
alias_method :spawn, :new
|
@@ -62,7 +98,7 @@ module Celluloid
|
|
62
98
|
|
63
99
|
proxy = Actor.new(allocate).proxy
|
64
100
|
current_actor.link proxy
|
65
|
-
proxy.send(:initialize, *args, &block)
|
101
|
+
proxy.send(:__send__, :initialize, *args, &block)
|
66
102
|
proxy
|
67
103
|
end
|
68
104
|
alias_method :spawn_link, :new_link
|
@@ -90,11 +126,26 @@ module Celluloid
|
|
90
126
|
# Configure a custom mailbox factory
|
91
127
|
def use_mailbox(klass = nil, &block)
|
92
128
|
if block
|
93
|
-
|
129
|
+
@mailbox_factory = block
|
130
|
+
else
|
131
|
+
@mailbox_factory = proc { klass.new }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Create a mailbox for this actor
|
136
|
+
def mailbox_factory
|
137
|
+
if defined?(@mailbox_factory)
|
138
|
+
@mailbox_factory.call
|
139
|
+
elsif defined?(super)
|
140
|
+
super
|
94
141
|
else
|
95
|
-
|
142
|
+
Mailbox.new
|
96
143
|
end
|
97
144
|
end
|
145
|
+
|
146
|
+
def ===(other)
|
147
|
+
other.kind_of? self
|
148
|
+
end
|
98
149
|
end
|
99
150
|
|
100
151
|
#
|
@@ -193,7 +244,13 @@ module Celluloid
|
|
193
244
|
Celluloid.sleep(interval)
|
194
245
|
end
|
195
246
|
|
196
|
-
#
|
247
|
+
# Run given block in an exclusive mode: all synchronous calls block the whole
|
248
|
+
# actor, not only current message processing.
|
249
|
+
def exclusive(&block)
|
250
|
+
Thread.current[:actor].exclusive(&block)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Call a block after a given interval, returning a Celluloid::Timer object
|
197
254
|
def after(interval, &block)
|
198
255
|
Thread.current[:actor].after(interval, &block)
|
199
256
|
end
|
@@ -207,11 +264,6 @@ module Celluloid
|
|
207
264
|
Future.new(&block).value
|
208
265
|
end
|
209
266
|
|
210
|
-
# Deprecated, do not use
|
211
|
-
def async
|
212
|
-
raise "Celluloid#async is defunct. Please use #defer instead"
|
213
|
-
end
|
214
|
-
|
215
267
|
# Process async calls via method_missing
|
216
268
|
def method_missing(meth, *args, &block)
|
217
269
|
# bang methods are async calls
|
@@ -245,6 +297,7 @@ require 'celluloid/fsm'
|
|
245
297
|
require 'celluloid/links'
|
246
298
|
require 'celluloid/logger'
|
247
299
|
require 'celluloid/mailbox'
|
300
|
+
require 'celluloid/pool'
|
248
301
|
require 'celluloid/receivers'
|
249
302
|
require 'celluloid/registry'
|
250
303
|
require 'celluloid/responses'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -22,58 +22,64 @@ module Celluloid
|
|
22
22
|
extend Registry
|
23
23
|
attr_reader :proxy, :tasks, :links, :mailbox
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
call
|
25
|
+
class << self
|
26
|
+
# Invoke a method on the given actor via its mailbox
|
27
|
+
def call(mailbox, meth, *args, &block)
|
28
|
+
call = SyncCall.new(Thread.mailbox, meth, args, block)
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
begin
|
31
|
+
mailbox << call
|
32
|
+
rescue MailboxError
|
33
|
+
raise DeadActorError, "attempted to call a dead actor"
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
if Celluloid.actor? and not Celluloid.exclusive?
|
37
|
+
# The current task will be automatically resumed when we get a response
|
38
|
+
Task.suspend(:callwait).value
|
39
|
+
else
|
40
|
+
# Otherwise we're inside a normal thread, so block
|
41
|
+
response = Thread.mailbox.receive do |msg|
|
42
|
+
msg.respond_to?(:call) and msg.call == call
|
43
|
+
end
|
44
|
+
|
45
|
+
response.value
|
42
46
|
end
|
47
|
+
end
|
43
48
|
|
44
|
-
|
49
|
+
# Invoke a method asynchronously on an actor via its mailbox
|
50
|
+
def async(mailbox, meth, *args, &block)
|
51
|
+
begin
|
52
|
+
mailbox << AsyncCall.new(Thread.mailbox, meth, args, block)
|
53
|
+
rescue MailboxError
|
54
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
55
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
56
|
+
# async calls should find other ways to deal with actors dying
|
57
|
+
# during an async call (i.e. linking/supervisors)
|
58
|
+
end
|
45
59
|
end
|
46
|
-
end
|
47
60
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# Silently swallow asynchronous calls to dead actors. There's no way
|
54
|
-
# to reliably generate DeadActorErrors for async calls, so users of
|
55
|
-
# async calls should find other ways to deal with actors dying
|
56
|
-
# during an async call (i.e. linking/supervisors)
|
61
|
+
# Call a method asynchronously and retrieve its value later
|
62
|
+
def future(mailbox, meth, *args, &block)
|
63
|
+
future = Future.new
|
64
|
+
future.execute(mailbox, meth, args, block)
|
65
|
+
future
|
57
66
|
end
|
58
|
-
end
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
# Obtain all running actors in the system
|
69
|
+
def all
|
70
|
+
actors = []
|
71
|
+
Thread.list.each do |t|
|
72
|
+
actor = t[:actor]
|
73
|
+
actors << actor.proxy if actor
|
74
|
+
end
|
75
|
+
actors
|
76
|
+
end
|
65
77
|
end
|
66
78
|
|
67
79
|
# Wrap the given subject with an Actor
|
68
80
|
def initialize(subject)
|
69
|
-
@subject
|
70
|
-
|
71
|
-
if subject.respond_to? :mailbox_factory
|
72
|
-
@mailbox = subject.mailbox_factory
|
73
|
-
else
|
74
|
-
@mailbox = Mailbox.new
|
75
|
-
end
|
76
|
-
|
81
|
+
@subject = subject
|
82
|
+
@mailbox = subject.class.mailbox_factory
|
77
83
|
@proxy = ActorProxy.new(@mailbox, subject.class.to_s)
|
78
84
|
@tasks = Set.new
|
79
85
|
@links = Links.new
|
@@ -81,11 +87,11 @@ module Celluloid
|
|
81
87
|
@receivers = Receivers.new
|
82
88
|
@timers = Timers.new
|
83
89
|
@running = true
|
90
|
+
@exclusive = false
|
84
91
|
|
85
92
|
@thread = ThreadPool.get do
|
86
93
|
Thread.current[:actor] = self
|
87
94
|
Thread.current[:mailbox] = @mailbox
|
88
|
-
|
89
95
|
run
|
90
96
|
end
|
91
97
|
end
|
@@ -95,6 +101,19 @@ module Celluloid
|
|
95
101
|
@running
|
96
102
|
end
|
97
103
|
|
104
|
+
# Is this actor running in exclusive mode?
|
105
|
+
def exclusive?
|
106
|
+
@exclusive
|
107
|
+
end
|
108
|
+
|
109
|
+
# Execute a code block in exclusive mode.
|
110
|
+
def exclusive
|
111
|
+
@exclusive = true
|
112
|
+
yield
|
113
|
+
ensure
|
114
|
+
@exclusive = false
|
115
|
+
end
|
116
|
+
|
98
117
|
# Terminate this actor
|
99
118
|
def terminate
|
100
119
|
@running = false
|
@@ -118,32 +137,30 @@ module Celluloid
|
|
118
137
|
|
119
138
|
# Run the actor loop
|
120
139
|
def run
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
140
|
+
begin
|
141
|
+
while @running
|
142
|
+
begin
|
143
|
+
message = @mailbox.receive(timeout)
|
144
|
+
rescue ExitEvent => exit_event
|
145
|
+
Task.new(:exit_handler) { handle_exit_event exit_event }.resume
|
146
|
+
retry
|
147
|
+
end
|
148
|
+
|
149
|
+
if message
|
150
|
+
handle_message message
|
151
|
+
else
|
152
|
+
# No message indicates a timeout
|
153
|
+
@timers.fire
|
154
|
+
@receivers.fire_timers
|
155
|
+
end
|
135
156
|
end
|
157
|
+
rescue MailboxShutdown
|
158
|
+
# If the mailbox detects shutdown, exit the actor
|
136
159
|
end
|
137
160
|
|
138
|
-
|
139
|
-
rescue
|
140
|
-
# If the mailbox detects shutdown, exit the actor
|
141
|
-
@running = false
|
142
|
-
rescue Exception => ex
|
143
|
-
@running = false
|
161
|
+
shutdown
|
162
|
+
rescue => ex
|
144
163
|
handle_crash(ex)
|
145
|
-
ensure
|
146
|
-
ThreadPool.put @thread
|
147
164
|
end
|
148
165
|
|
149
166
|
# How long to wait until the next timer fires
|
@@ -169,9 +186,13 @@ module Celluloid
|
|
169
186
|
|
170
187
|
# Sleep for the given amount of time
|
171
188
|
def sleep(interval)
|
172
|
-
|
173
|
-
|
174
|
-
|
189
|
+
if Celluloid.exclusive?
|
190
|
+
Kernel.sleep(interval)
|
191
|
+
else
|
192
|
+
task = Task.current
|
193
|
+
@timers.add(interval) { task.resume }
|
194
|
+
Task.suspend :sleeping
|
195
|
+
end
|
175
196
|
end
|
176
197
|
|
177
198
|
# Handle an incoming message
|
@@ -180,7 +201,7 @@ module Celluloid
|
|
180
201
|
when Call
|
181
202
|
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
182
203
|
when Response
|
183
|
-
message.call.task.resume message
|
204
|
+
message.call.task.resume message
|
184
205
|
else
|
185
206
|
@receivers.handle_message(message)
|
186
207
|
end
|
@@ -202,22 +223,34 @@ module Celluloid
|
|
202
223
|
# Handle any exceptions that occur within a running actor
|
203
224
|
def handle_crash(exception)
|
204
225
|
Logger.crash("#{@subject.class} crashed!", exception)
|
205
|
-
|
206
|
-
rescue
|
226
|
+
shutdown ExitEvent.new(@proxy, exception)
|
227
|
+
rescue => ex
|
207
228
|
Logger.crash("#{@subject.class}: ERROR HANDLER CRASHED!", ex)
|
208
229
|
end
|
209
230
|
|
210
231
|
# Handle cleaning up this actor after it exits
|
232
|
+
def shutdown(exit_event = ExitEvent.new(@proxy))
|
233
|
+
run_finalizer
|
234
|
+
cleanup exit_event
|
235
|
+
ensure
|
236
|
+
Thread.current[:actor] = nil
|
237
|
+
Thread.current[:mailbox] = nil
|
238
|
+
end
|
239
|
+
|
240
|
+
# Run the user-defined finalizer, if one is set
|
241
|
+
def run_finalizer
|
242
|
+
@subject.finalize if @subject.respond_to? :finalize
|
243
|
+
rescue => ex
|
244
|
+
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Clean up after this actor
|
211
248
|
def cleanup(exit_event)
|
212
249
|
@mailbox.shutdown
|
213
250
|
@links.send_event exit_event
|
214
251
|
tasks.each { |task| task.terminate }
|
215
|
-
|
216
|
-
|
217
|
-
@subject.finalize if @subject.respond_to? :finalize
|
218
|
-
rescue Exception => ex
|
219
|
-
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
220
|
-
end
|
252
|
+
rescue => ex
|
253
|
+
Logger.crash("#{@subject.class}: CLEANUP CRASHED!", ex)
|
221
254
|
end
|
222
255
|
end
|
223
256
|
end
|
@@ -17,6 +17,14 @@ module Celluloid
|
|
17
17
|
Actor.call @mailbox, :send, :class
|
18
18
|
end
|
19
19
|
|
20
|
+
def is_a?(klass)
|
21
|
+
Actor.call @mailbox, :is_a?, klass
|
22
|
+
end
|
23
|
+
|
24
|
+
def kind_of?(klass)
|
25
|
+
Actor.call @mailbox, :kind_of?, klass
|
26
|
+
end
|
27
|
+
|
20
28
|
def respond_to?(meth)
|
21
29
|
Actor.call @mailbox, :respond_to?, meth
|
22
30
|
end
|
data/lib/celluloid/future.rb
CHANGED
data/lib/celluloid/group.rb
CHANGED
@@ -52,6 +52,9 @@ module Celluloid
|
|
52
52
|
supervisable = @supervisors.delete supervisor
|
53
53
|
raise "a supervisable went missing. This shouldn't be!" unless supervisable
|
54
54
|
|
55
|
+
# Ignore supervisors that shut down cleanly
|
56
|
+
return unless reason
|
57
|
+
|
55
58
|
supervisor = supervisable.supervise
|
56
59
|
@supervisors[supervisor] = supervisable
|
57
60
|
end
|
data/lib/celluloid/logger.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Celluloid
|
2
2
|
module Logger
|
3
|
+
@exception_handlers = []
|
3
4
|
module_function
|
4
5
|
|
5
6
|
# Send a debug message
|
@@ -24,9 +25,29 @@ module Celluloid
|
|
24
25
|
|
25
26
|
# Handle a crash
|
26
27
|
def crash(string, exception)
|
27
|
-
string
|
28
|
-
string
|
29
|
-
|
28
|
+
string << "\n" << format_exception(exception)
|
29
|
+
error string
|
30
|
+
|
31
|
+
@exception_handlers.each do |handler|
|
32
|
+
begin
|
33
|
+
handler.call(exception)
|
34
|
+
rescue => ex
|
35
|
+
error "EXCEPTION HANDLER CRASHED:\n" << format_exception(ex)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Format an exception message
|
41
|
+
def format_exception(exception)
|
42
|
+
str = "#{exception.class}: #{exception.to_s}\n"
|
43
|
+
str << exception.backtrace.join("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Define an exception handler
|
47
|
+
# NOTE: These should be defined at application start time
|
48
|
+
def exception_handler(&block)
|
49
|
+
@exception_handlers << block
|
50
|
+
nil
|
30
51
|
end
|
31
52
|
end
|
32
53
|
end
|
data/lib/celluloid/mailbox.rb
CHANGED
@@ -14,59 +14,65 @@ module Celluloid
|
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@messages = []
|
17
|
-
@
|
17
|
+
@mutex = Mutex.new
|
18
18
|
@dead = false
|
19
19
|
@condition = ConditionVariable.new
|
20
20
|
end
|
21
21
|
|
22
22
|
# Add a message to the Mailbox
|
23
23
|
def <<(message)
|
24
|
-
@
|
25
|
-
|
24
|
+
@mutex.lock
|
25
|
+
begin
|
26
|
+
raise MailboxError, "dead recipient" if @dead
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
@messages << message
|
29
|
+
@condition.signal
|
30
|
+
nil
|
31
|
+
ensure @mutex.unlock
|
32
|
+
end
|
31
33
|
end
|
32
34
|
|
33
35
|
# Add a high-priority system event to the Mailbox
|
34
36
|
def system_event(event)
|
35
|
-
@
|
36
|
-
|
37
|
-
@messages
|
38
|
-
|
37
|
+
@mutex.lock
|
38
|
+
begin
|
39
|
+
unless @dead # Silently fail if messages are sent to dead actors
|
40
|
+
@messages.unshift event
|
41
|
+
@condition.signal
|
42
|
+
end
|
43
|
+
nil
|
44
|
+
ensure @mutex.unlock
|
39
45
|
end
|
40
|
-
nil
|
41
|
-
ensure @lock.unlock
|
42
46
|
end
|
43
47
|
|
44
48
|
# Receive a message from the Mailbox
|
45
49
|
def receive(timeout = nil, &block)
|
46
50
|
message = nil
|
47
51
|
|
48
|
-
@
|
49
|
-
raise MailboxError, "attempted to receive from a dead mailbox" if @dead
|
50
|
-
|
52
|
+
@mutex.lock
|
51
53
|
begin
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
54
|
+
raise MailboxError, "attempted to receive from a dead mailbox" if @dead
|
55
|
+
|
56
|
+
begin
|
57
|
+
message = next_message(&block)
|
58
|
+
|
59
|
+
unless message
|
60
|
+
if timeout
|
61
|
+
now = Time.now
|
62
|
+
wait_until ||= now + timeout
|
63
|
+
wait_interval = wait_until - now
|
64
|
+
return if wait_interval <= 0
|
65
|
+
else
|
66
|
+
wait_interval = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
@condition.wait(@mutex, wait_interval)
|
62
70
|
end
|
71
|
+
end until message
|
63
72
|
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
message
|
69
|
-
ensure @lock.unlock
|
73
|
+
message
|
74
|
+
ensure @mutex.unlock
|
75
|
+
end
|
70
76
|
end
|
71
77
|
|
72
78
|
# Retrieve the next message in the mailbox
|
@@ -89,16 +95,16 @@ module Celluloid
|
|
89
95
|
|
90
96
|
# Shut down this mailbox and clean up its contents
|
91
97
|
def shutdown
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
@
|
98
|
+
@mutex.lock
|
99
|
+
begin
|
100
|
+
messages = @messages
|
101
|
+
@messages = []
|
102
|
+
@dead = true
|
103
|
+
ensure @mutex.unlock
|
104
|
+
end
|
98
105
|
|
99
106
|
messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
|
100
107
|
true
|
101
|
-
ensure @lock.unlock
|
102
108
|
end
|
103
109
|
|
104
110
|
# Is the mailbox alive?
|
@@ -108,9 +114,7 @@ module Celluloid
|
|
108
114
|
|
109
115
|
# Cast to an array
|
110
116
|
def to_a
|
111
|
-
@
|
112
|
-
@messages.dup
|
113
|
-
ensure @lock.unlock
|
117
|
+
@mutex.synchronize { @messages.dup }
|
114
118
|
end
|
115
119
|
|
116
120
|
# Iterate through the mailbox
|
@@ -0,0 +1,103 @@
|
|
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
|
+
|
7
|
+
# Takes a class of actor to pool and a hash of options:
|
8
|
+
#
|
9
|
+
# * initial_size: how many actors to eagerly create
|
10
|
+
# * max_size: maximum number of actors (default nil, unlimited)
|
11
|
+
# * args: an array of arguments to pass to the actor's initialize
|
12
|
+
def initialize(klass, options = {})
|
13
|
+
opts = {
|
14
|
+
:initial_size => 1,
|
15
|
+
:max_size => nil,
|
16
|
+
:args => []
|
17
|
+
}.merge(options)
|
18
|
+
|
19
|
+
@klass, @args = klass, opts[:args]
|
20
|
+
@max_actors = opts[:max_size]
|
21
|
+
@idle_actors, @running_actors = 0, 0
|
22
|
+
@actors = []
|
23
|
+
|
24
|
+
opts[:initial_size].times do
|
25
|
+
@actors << spawn
|
26
|
+
@idle_actors += 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get an actor from the pool. Actors taken from the pool must be put back
|
31
|
+
# with Pool#put. Alternatively, you can use get with a block form:
|
32
|
+
#
|
33
|
+
# pool.get { |actor| ... }
|
34
|
+
#
|
35
|
+
# This will automatically return actors to the pool when the block completes
|
36
|
+
def get
|
37
|
+
if @max_actors and @running_actors == @max_actors
|
38
|
+
wait :ready
|
39
|
+
end
|
40
|
+
|
41
|
+
actor = @actors.pop
|
42
|
+
if actor
|
43
|
+
@idle_actors -= 1
|
44
|
+
else
|
45
|
+
actor = spawn
|
46
|
+
end
|
47
|
+
|
48
|
+
if block_given?
|
49
|
+
begin
|
50
|
+
yield actor
|
51
|
+
rescue => ex
|
52
|
+
end
|
53
|
+
|
54
|
+
put actor
|
55
|
+
abort ex if ex
|
56
|
+
nil
|
57
|
+
else
|
58
|
+
actor
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return an actor to the pool
|
63
|
+
def put(actor)
|
64
|
+
begin
|
65
|
+
raise TypeError, "expecting a #{@klass} actor" unless actor.is_a? @klass
|
66
|
+
rescue DeadActorError
|
67
|
+
# The actor may have died before it was handed back to us
|
68
|
+
# We'll let the crash_handler deal with it in due time
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
@actors << actor
|
73
|
+
@idle_actors += 1
|
74
|
+
end
|
75
|
+
|
76
|
+
# Number of active actors in this pool
|
77
|
+
def size
|
78
|
+
@running_actors
|
79
|
+
end
|
80
|
+
|
81
|
+
# Number of idle actors in the pool
|
82
|
+
def idle_count
|
83
|
+
@idle_actors
|
84
|
+
end
|
85
|
+
alias_method :idle_size, :idle_count
|
86
|
+
|
87
|
+
# Handle crashed actors
|
88
|
+
def crash_handler(actor, reason)
|
89
|
+
@idle_actors -= 1 if @actors.delete actor
|
90
|
+
@running_actors -= 1
|
91
|
+
|
92
|
+
# If we were maxed out before...
|
93
|
+
signal :ready if @max_actors and @running_actors + 1 == @max_actors
|
94
|
+
end
|
95
|
+
|
96
|
+
# Spawn an actor of the given class
|
97
|
+
def spawn
|
98
|
+
worker = @klass.new_link(*@args)
|
99
|
+
@running_actors += 1
|
100
|
+
worker
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/celluloid/receivers.rb
CHANGED
@@ -10,17 +10,21 @@ module Celluloid
|
|
10
10
|
|
11
11
|
# Receive an asynchronous message
|
12
12
|
def receive(timeout = nil, &block)
|
13
|
-
|
13
|
+
if Celluloid.exclusive?
|
14
|
+
Thread.mailbox.receive(timeout, &block)
|
15
|
+
else
|
16
|
+
receiver = Receiver.new block
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
if timeout
|
19
|
+
receiver.timer = @timers.add(timeout) do
|
20
|
+
@receivers.delete receiver
|
21
|
+
receiver.resume
|
22
|
+
end
|
19
23
|
end
|
20
|
-
end
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
@receivers << receiver
|
26
|
+
Task.suspend :receiving
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
# How long to wait until the next timer fires
|
data/lib/celluloid/signals.rb
CHANGED
@@ -9,8 +9,9 @@ module Celluloid
|
|
9
9
|
|
10
10
|
# Wait for the given signal and return the associated value
|
11
11
|
def wait(signal)
|
12
|
-
|
12
|
+
raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
|
13
13
|
|
14
|
+
tasks = @waiting[signal]
|
14
15
|
case tasks
|
15
16
|
when Array
|
16
17
|
tasks << Task.current
|
data/lib/celluloid/supervisor.rb
CHANGED
@@ -28,7 +28,7 @@ module Celluloid
|
|
28
28
|
|
29
29
|
begin
|
30
30
|
@actor = @klass.new_link(*@args, &@block)
|
31
|
-
rescue
|
31
|
+
rescue
|
32
32
|
failures += 1
|
33
33
|
if failures >= start_attempts
|
34
34
|
failures = 0
|
@@ -45,6 +45,9 @@ module Celluloid
|
|
45
45
|
|
46
46
|
# When actors die, regardless of the reason, restart them
|
47
47
|
def restart_actor(actor, reason)
|
48
|
+
# If the actor we're supervising exited cleanly, exit the supervisor cleanly too
|
49
|
+
terminate unless reason
|
50
|
+
|
48
51
|
start_actor if @started
|
49
52
|
end
|
50
53
|
|
@@ -4,7 +4,7 @@ module Celluloid
|
|
4
4
|
# Maintain a thread pool FOR SPEED!!
|
5
5
|
module ThreadPool
|
6
6
|
@pool = []
|
7
|
-
@
|
7
|
+
@mutex = Mutex.new
|
8
8
|
|
9
9
|
# TODO: should really adjust this based on usage
|
10
10
|
@max_idle = 16
|
@@ -14,7 +14,7 @@ module Celluloid
|
|
14
14
|
|
15
15
|
# Get a thread from the pool, running the given block
|
16
16
|
def get(&block)
|
17
|
-
@
|
17
|
+
@mutex.synchronize do
|
18
18
|
if @pool.empty?
|
19
19
|
thread = create
|
20
20
|
else
|
@@ -28,7 +28,7 @@ module Celluloid
|
|
28
28
|
|
29
29
|
# Return a thread to the pool
|
30
30
|
def put(thread)
|
31
|
-
@
|
31
|
+
@mutex.synchronize do
|
32
32
|
if @pool.size >= @max_idle
|
33
33
|
thread[:queue] << nil
|
34
34
|
else
|
@@ -41,14 +41,17 @@ module Celluloid
|
|
41
41
|
def create
|
42
42
|
queue = Queue.new
|
43
43
|
thread = Thread.new do
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
while proc = queue.pop
|
45
|
+
begin
|
46
|
+
proc.call
|
47
|
+
rescue => ex
|
48
|
+
Logger.crash("thread crashed", ex)
|
47
49
|
end
|
48
|
-
|
49
|
-
|
50
|
+
|
51
|
+
put thread
|
50
52
|
end
|
51
53
|
end
|
54
|
+
|
52
55
|
thread[:queue] = queue
|
53
56
|
thread
|
54
57
|
end
|
data/lib/celluloid/version.rb
CHANGED
@@ -49,6 +49,15 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
49
49
|
actor.class.should == actor_class
|
50
50
|
end
|
51
51
|
|
52
|
+
it "compares with the actor's class in a case statement" do
|
53
|
+
case actor_class.new("Troy McClure")
|
54
|
+
when actor_class
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end.should be_true
|
59
|
+
end
|
60
|
+
|
52
61
|
it "handles synchronous calls" do
|
53
62
|
actor = actor_class.new "Troy McClure"
|
54
63
|
actor.greet.should == "Hi, I'm Troy McClure"
|
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.9.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-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70248586219120 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,21 +21,21 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70248586219120
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70248586218540 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
|
-
- -
|
30
|
+
- - ! '>='
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70248586218540
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: benchmark_suite
|
38
|
-
requirement: &
|
38
|
+
requirement: &70248586217360 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70248586217360
|
47
47
|
description: Celluloid is a concurrent object framework inspired by the Actor Model
|
48
48
|
email:
|
49
49
|
- tony.arcieri@gmail.com
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- lib/celluloid/links.rb
|
65
65
|
- lib/celluloid/logger.rb
|
66
66
|
- lib/celluloid/mailbox.rb
|
67
|
+
- lib/celluloid/pool.rb
|
67
68
|
- lib/celluloid/receivers.rb
|
68
69
|
- lib/celluloid/registry.rb
|
69
70
|
- lib/celluloid/responses.rb
|