celluloid 0.8.0 → 0.9.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 +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
|
+

|
2
2
|
=========
|
3
|
-
[](http://travis-ci.org/tarcieri/celluloid)
|
4
|
+
[](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
|