celluloid 0.2.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +214 -67
- data/lib/celluloid.rb +144 -13
- data/lib/celluloid/actor.rb +152 -246
- data/lib/celluloid/actor_pool.rb +57 -0
- data/lib/celluloid/actor_proxy.rb +29 -19
- data/lib/celluloid/core_ext.rb +9 -2
- data/lib/celluloid/io.rb +50 -0
- data/lib/celluloid/io/actor.rb +10 -0
- data/lib/celluloid/io/mailbox.rb +65 -0
- data/lib/celluloid/io/reactor.rb +63 -0
- data/lib/celluloid/io/waker.rb +43 -0
- data/lib/celluloid/linking.rb +24 -20
- data/lib/celluloid/mailbox.rb +43 -30
- data/lib/celluloid/registry.rb +0 -5
- data/lib/celluloid/rspec.rb +2 -0
- data/lib/celluloid/supervisor.rb +4 -4
- data/lib/celluloid/tcp_server.rb +32 -0
- data/lib/celluloid/version.rb +1 -1
- metadata +45 -45
data/lib/celluloid.rb
CHANGED
@@ -1,24 +1,150 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
|
-
module Celluloid
|
4
|
-
|
5
|
-
@@logger = Logger.new STDERR
|
3
|
+
module Celluloid
|
4
|
+
@logger = Logger.new STDERR
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
class << self
|
7
|
+
attr_accessor :logger # Thread-safe logger class
|
8
|
+
|
9
|
+
def included(klass)
|
10
|
+
klass.send :extend, ClassMethods
|
11
|
+
klass.send :include, Linking
|
12
|
+
end
|
13
|
+
|
14
|
+
# Are we currently inside of an actor?
|
15
|
+
def actor?
|
16
|
+
!!Thread.current[:actor]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Obtain the currently running actor (if one exists)
|
20
|
+
def current_actor
|
21
|
+
actor = Thread.current[:actor_proxy]
|
22
|
+
raise NotActorError, "not in actor scope" unless actor
|
23
|
+
|
24
|
+
actor
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Class methods added to classes which include Celluloid
|
29
|
+
module ClassMethods
|
30
|
+
# Create a new actor
|
31
|
+
def new(*args, &block)
|
32
|
+
proxy = Celluloid::Actor.new(allocate).proxy
|
33
|
+
proxy.send(:initialize, *args, &block)
|
34
|
+
proxy
|
35
|
+
end
|
36
|
+
alias_method :spawn, :new
|
37
|
+
|
38
|
+
# Create a new actor and link to the current one
|
39
|
+
def new_link(*args, &block)
|
40
|
+
current_actor = Thread.current[:actor]
|
41
|
+
raise NotActorError, "can't link outside actor context" unless current_actor
|
42
|
+
|
43
|
+
proxy = Celluloid::Actor.new(allocate).proxy
|
44
|
+
current_actor.link proxy
|
45
|
+
proxy.send(:initialize, *args, &block)
|
46
|
+
proxy
|
47
|
+
end
|
48
|
+
alias_method :spawn_link, :new_link
|
49
|
+
|
50
|
+
# Create a supervisor which ensures an instance of an actor will restart
|
51
|
+
# an actor if it fails
|
52
|
+
def supervise(*args, &block)
|
53
|
+
Celluloid::Supervisor.supervise(self, *args, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create a supervisor which ensures an instance of an actor will restart
|
57
|
+
# an actor if it fails, and keep the actor registered under a given name
|
58
|
+
def supervise_as(name, *args, &block)
|
59
|
+
Celluloid::Supervisor.supervise_as(name, self, *args, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Trap errors from actors we're linked to when they exit
|
63
|
+
def trap_exit(callback)
|
64
|
+
@exit_handler = callback.to_sym
|
65
|
+
end
|
66
|
+
|
67
|
+
# Obtain the exit handler method for this class
|
68
|
+
def exit_handler; @exit_handler; end
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Instance methods
|
73
|
+
#
|
74
|
+
|
75
|
+
# Is this actor alive?
|
76
|
+
def alive?
|
77
|
+
Thread.current[:actor].alive?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Raise an exception in caller context, but stay running
|
81
|
+
def abort(cause)
|
82
|
+
raise AbortError.new(cause)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Terminate this actor
|
86
|
+
def terminate
|
87
|
+
Thread.current[:actor].terminate
|
9
88
|
end
|
10
|
-
|
11
|
-
def
|
12
|
-
|
89
|
+
|
90
|
+
def inspect
|
91
|
+
str = "#<Celluloid::Actor(#{self.class}:0x#{object_id.to_s(16)})"
|
92
|
+
ivars = instance_variables.map do |ivar|
|
93
|
+
"#{ivar}=#{instance_variable_get(ivar).inspect}"
|
94
|
+
end
|
95
|
+
|
96
|
+
str << " " << ivars.join(' ') unless ivars.empty?
|
97
|
+
str << ">"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Send a signal with the given name to all waiting methods
|
101
|
+
def signal(name, value = nil)
|
102
|
+
Thread.current[:actor].signal name, value
|
103
|
+
end
|
104
|
+
|
105
|
+
# Wait for the given signal
|
106
|
+
def wait(name)
|
107
|
+
Thread.current[:actor].wait name
|
13
108
|
end
|
14
|
-
|
15
|
-
|
16
|
-
|
109
|
+
|
110
|
+
# Obtain the current_actor
|
111
|
+
def current_actor
|
112
|
+
Celluloid.current_actor
|
113
|
+
end
|
114
|
+
|
115
|
+
# Perform a blocking or computationally intensive action inside an
|
116
|
+
# asynchronous thread pool, allowing the caller to continue processing other
|
117
|
+
# messages in its mailbox in the meantime
|
118
|
+
def async(&block)
|
119
|
+
# This implementation relies on the present implementation of
|
120
|
+
# Celluloid::Future, which uses a Celluloid::Actor to run the block
|
121
|
+
Celluloid::Future.new(&block).value
|
122
|
+
end
|
123
|
+
|
124
|
+
# Process async calls via method_missing
|
125
|
+
def method_missing(meth, *args, &block)
|
126
|
+
# bang methods are async calls
|
127
|
+
if meth.to_s.match(/!$/)
|
128
|
+
unbanged_meth = meth.to_s.sub(/!$/, '')
|
129
|
+
call = AsyncCall.new(@mailbox, unbanged_meth, args, block)
|
130
|
+
|
131
|
+
begin
|
132
|
+
Thread.current[:actor].mailbox << call
|
133
|
+
rescue MailboxError
|
134
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
135
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
136
|
+
# async calls should find other ways to deal with actors dying
|
137
|
+
# during an async call (i.e. linking/supervisors)
|
138
|
+
end
|
139
|
+
|
140
|
+
return # casts are async and return immediately
|
141
|
+
end
|
142
|
+
|
143
|
+
super
|
17
144
|
end
|
18
145
|
end
|
19
146
|
|
20
147
|
require 'celluloid/version'
|
21
|
-
require 'celluloid/actor'
|
22
148
|
require 'celluloid/actor_proxy'
|
23
149
|
require 'celluloid/calls'
|
24
150
|
require 'celluloid/core_ext'
|
@@ -28,6 +154,11 @@ require 'celluloid/mailbox'
|
|
28
154
|
require 'celluloid/registry'
|
29
155
|
require 'celluloid/responses'
|
30
156
|
require 'celluloid/signals'
|
31
|
-
require 'celluloid/supervisor'
|
32
157
|
|
158
|
+
require 'celluloid/actor'
|
159
|
+
require 'celluloid/actor_pool'
|
160
|
+
require 'celluloid/supervisor'
|
33
161
|
require 'celluloid/future'
|
162
|
+
|
163
|
+
require 'celluloid/io'
|
164
|
+
require 'celluloid/tcp_server'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -3,8 +3,10 @@ require 'thread'
|
|
3
3
|
begin
|
4
4
|
require 'fiber'
|
5
5
|
rescue LoadError => ex
|
6
|
-
|
7
|
-
|
6
|
+
if defined? JRUBY_VERSION
|
7
|
+
raise LoadError, "Celluloid requires JRuby 1.9 mode. Please pass the --1.9 flag or set JRUBY_OPTS=--1.9"
|
8
|
+
elsif defined? Rubinius
|
9
|
+
# If we're on Rubinius, we can still work in 1.8 mode
|
8
10
|
Fiber = Rubinius::Fiber
|
9
11
|
else
|
10
12
|
raise ex
|
@@ -12,271 +14,175 @@ rescue LoadError => ex
|
|
12
14
|
end
|
13
15
|
|
14
16
|
module Celluloid
|
15
|
-
#
|
17
|
+
# Don't do Actor-like things outside Actor scope
|
16
18
|
class NotActorError < StandardError; end
|
17
|
-
|
18
|
-
#
|
19
|
+
|
20
|
+
# Trying to do something to a dead actor
|
19
21
|
class DeadActorError < StandardError; end
|
20
|
-
|
21
|
-
#
|
22
|
+
|
23
|
+
# The caller made an error, not the current actor
|
22
24
|
class AbortError < StandardError
|
23
25
|
attr_reader :cause
|
24
|
-
|
26
|
+
|
25
27
|
def initialize(cause)
|
26
28
|
@cause = cause
|
27
29
|
super "caused by #{cause.inspect}: #{cause.to_s}"
|
28
30
|
end
|
29
31
|
end
|
30
|
-
|
31
|
-
# Are we currently inside of an actor?
|
32
|
-
def self.actor?
|
33
|
-
!!Thread.current[:actor]
|
34
|
-
end
|
35
|
-
|
36
|
-
# Obtain the currently running actor (if one exists)
|
37
|
-
def self.current_actor
|
38
|
-
actor = Thread.current[:actor_proxy]
|
39
|
-
raise NotActorError, "not in actor scope" unless actor
|
40
|
-
|
41
|
-
actor
|
42
|
-
end
|
43
|
-
|
32
|
+
|
44
33
|
# Actors are Celluloid's concurrency primitive. They're implemented as
|
45
34
|
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
46
|
-
# messages.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# Create a new actor
|
51
|
-
def spawn(*args, &block)
|
52
|
-
actor = allocate
|
53
|
-
proxy = actor.__start_actor
|
54
|
-
proxy.send(:initialize, *args, &block)
|
55
|
-
proxy
|
56
|
-
end
|
57
|
-
alias_method :new, :spawn
|
58
|
-
|
59
|
-
# Create a new actor and link to the current one
|
60
|
-
def spawn_link(*args, &block)
|
61
|
-
current_actor = Thread.current[:actor]
|
62
|
-
raise NotActorError, "can't link outside actor context" unless current_actor
|
35
|
+
# messages.
|
36
|
+
class Actor
|
37
|
+
extend Registry
|
38
|
+
include Linking
|
63
39
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
Celluloid::Supervisor.supervise_as(name, self, *args, &block)
|
40
|
+
attr_reader :proxy
|
41
|
+
attr_reader :links
|
42
|
+
attr_reader :mailbox
|
43
|
+
|
44
|
+
|
45
|
+
# Wrap the given subject object with an Actor
|
46
|
+
def initialize(subject)
|
47
|
+
@subject = subject
|
48
|
+
@mailbox = initialize_mailbox
|
49
|
+
@links = Links.new
|
50
|
+
@signals = Signals.new
|
51
|
+
@proxy = ActorProxy.new(self, @mailbox)
|
52
|
+
@running = true
|
53
|
+
|
54
|
+
@thread = Pool.get
|
55
|
+
@thread[:queue] << proc do
|
56
|
+
initialize_thread_locals
|
57
|
+
run
|
83
58
|
end
|
84
|
-
|
85
|
-
# Trap errors from actors we're linked to when they exit
|
86
|
-
def trap_exit(callback)
|
87
|
-
@_exit_handler = callback.to_sym
|
88
|
-
end
|
89
|
-
|
90
|
-
# Obtain the exit handler method for this class
|
91
|
-
def exit_handler; @_exit_handler; end
|
92
59
|
end
|
93
|
-
|
94
|
-
# Instance methods added to the public API
|
95
|
-
module InstanceMethods
|
96
|
-
# Obtain the mailbox of this actor
|
97
|
-
def mailbox; @_mailbox; end
|
98
|
-
|
99
|
-
# Is this actor alive?
|
100
|
-
def alive?
|
101
|
-
@_thread.alive?
|
102
|
-
end
|
103
|
-
|
104
|
-
# Raise an exception in caller context, but stay running
|
105
|
-
def abort(cause)
|
106
|
-
raise AbortError.new(cause)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Terminate this actor
|
110
|
-
def terminate
|
111
|
-
@_running = false
|
112
|
-
end
|
113
|
-
|
114
|
-
def inspect
|
115
|
-
str = "#<Celluloid::Actor(#{self.class}:0x#{self.object_id.to_s(16)})"
|
116
|
-
|
117
|
-
ivars = []
|
118
|
-
instance_variables.each do |ivar|
|
119
|
-
ivar_name = ivar.to_s.sub(/^@_/, '')
|
120
|
-
next if %w(mailbox links signals proxy thread running).include? ivar_name
|
121
|
-
ivars << "#{ivar}=#{instance_variable_get(ivar).inspect}"
|
122
|
-
end
|
123
|
-
|
124
|
-
str << " " << ivars.join(' ') unless ivars.empty?
|
125
|
-
str << ">"
|
126
|
-
end
|
127
|
-
|
128
|
-
#
|
129
|
-
# Signals
|
130
|
-
#
|
131
|
-
|
132
|
-
# Send a signal with the given name to all waiting methods
|
133
|
-
def signal(name, value = nil)
|
134
|
-
@_signals.send name, value
|
135
|
-
end
|
136
|
-
|
137
|
-
# Wait for the given signal
|
138
|
-
def wait(name)
|
139
|
-
@_signals.wait name
|
140
|
-
end
|
141
|
-
|
142
|
-
#
|
143
|
-
# Async calls
|
144
|
-
#
|
145
|
-
|
146
|
-
def method_missing(meth, *args, &block)
|
147
|
-
# bang methods are async calls
|
148
|
-
if meth.to_s.match(/!$/)
|
149
|
-
unbanged_meth = meth.to_s.sub(/!$/, '')
|
150
60
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
61
|
+
# Create the mailbox for this actor
|
62
|
+
#
|
63
|
+
# This implemententation is intended to be overridden in special-case
|
64
|
+
# subclasses of Celluloid::Actor which use a custom mailbox
|
65
|
+
def initialize_mailbox
|
66
|
+
Mailbox.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# Configure thread locals for the running thread
|
70
|
+
def initialize_thread_locals
|
71
|
+
Thread.current[:actor] = self
|
72
|
+
Thread.current[:actor_proxy] = @proxy
|
73
|
+
Thread.current[:mailbox] = @mailbox
|
74
|
+
end
|
75
|
+
|
76
|
+
# Run the actor loop
|
77
|
+
def run
|
78
|
+
process_messages
|
79
|
+
cleanup ExitEvent.new(@proxy)
|
80
|
+
rescue Exception => ex
|
81
|
+
@running = false
|
82
|
+
handle_crash(ex)
|
83
|
+
ensure
|
84
|
+
Pool.put @thread
|
85
|
+
end
|
86
|
+
|
87
|
+
# Is this actor alive?
|
88
|
+
def alive?
|
89
|
+
@running
|
90
|
+
end
|
91
|
+
|
92
|
+
# Terminate this actor
|
93
|
+
def terminate
|
94
|
+
@running = false
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Send a signal with the given name to all waiting methods
|
99
|
+
def signal(name, value = nil)
|
100
|
+
@signals.send name, value
|
101
|
+
end
|
102
|
+
|
103
|
+
# Wait for the given signal
|
104
|
+
def wait(name)
|
105
|
+
@signals.wait name
|
165
106
|
end
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
107
|
+
|
108
|
+
#######
|
109
|
+
private
|
110
|
+
#######
|
111
|
+
|
112
|
+
# Process incoming messages
|
113
|
+
def process_messages
|
114
|
+
pending_calls = {}
|
115
|
+
|
116
|
+
while @running
|
117
|
+
begin
|
118
|
+
message = @mailbox.receive
|
119
|
+
rescue MailboxShutdown
|
120
|
+
# If the mailbox detects shutdown, exit the actor
|
121
|
+
@running = false
|
122
|
+
rescue ExitEvent => exit_event
|
123
|
+
fiber = Fiber.new do
|
124
|
+
initialize_thread_locals
|
125
|
+
handle_exit_event exit_event
|
126
|
+
end
|
127
|
+
|
128
|
+
call = fiber.resume
|
129
|
+
pending_calls[call] = fiber if fiber.alive?
|
130
|
+
|
131
|
+
retry
|
180
132
|
end
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
Thread.current[:actor] = self
|
188
|
-
Thread.current[:actor_proxy] = @_proxy
|
189
|
-
Thread.current[:mailbox] = @_mailbox
|
190
|
-
end
|
191
|
-
|
192
|
-
# Run the actor
|
193
|
-
def __run_actor
|
194
|
-
__process_messages
|
195
|
-
__cleanup ExitEvent.new(@_proxy)
|
196
|
-
rescue Exception => ex
|
197
|
-
__handle_crash(ex)
|
198
|
-
ensure
|
199
|
-
Thread.current.exit
|
200
|
-
end
|
201
|
-
|
202
|
-
# Process incoming messages
|
203
|
-
def __process_messages
|
204
|
-
pending_calls = {}
|
205
|
-
|
206
|
-
while @_running
|
207
|
-
begin
|
208
|
-
message = @_mailbox.receive
|
209
|
-
rescue ExitEvent => exit_event
|
210
|
-
fiber = Fiber.new do
|
211
|
-
__init_thread
|
212
|
-
__handle_exit_event exit_event
|
213
|
-
end
|
214
|
-
|
215
|
-
call = fiber.resume
|
216
|
-
pending_calls[call] = fiber if fiber.alive?
|
217
|
-
|
218
|
-
retry
|
133
|
+
|
134
|
+
case message
|
135
|
+
when Call
|
136
|
+
fiber = Fiber.new do
|
137
|
+
initialize_thread_locals
|
138
|
+
message.dispatch(@subject)
|
219
139
|
end
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
call = fiber.resume
|
140
|
+
|
141
|
+
call = fiber.resume
|
142
|
+
pending_calls[call] = fiber if fiber.alive?
|
143
|
+
when Response
|
144
|
+
fiber = pending_calls.delete(message.call)
|
145
|
+
|
146
|
+
if fiber
|
147
|
+
call = fiber.resume message
|
229
148
|
pending_calls[call] = fiber if fiber.alive?
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
if fiber
|
234
|
-
call = fiber.resume message
|
235
|
-
pending_calls[call] = fiber if fiber.alive?
|
236
|
-
end
|
237
|
-
end # unexpected messages are ignored
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# Handle exit events received by this actor
|
242
|
-
def __handle_exit_event(exit_event)
|
243
|
-
exit_handler = self.class.exit_handler
|
244
|
-
if exit_handler
|
245
|
-
return send(exit_handler, exit_event.actor, exit_event.reason)
|
246
|
-
end
|
247
|
-
|
248
|
-
# Reraise exceptions from linked actors
|
249
|
-
# If no reason is given, actor terminated cleanly
|
250
|
-
raise exit_event.reason if exit_event.reason
|
251
|
-
end
|
252
|
-
|
253
|
-
# Handle any exceptions that occur within a running actor
|
254
|
-
def __handle_crash(exception)
|
255
|
-
__log_error(exception)
|
256
|
-
__cleanup ExitEvent.new(@_proxy, exception)
|
257
|
-
rescue Exception => handler_exception
|
258
|
-
__log_error(handler_exception, "ERROR HANDLER CRASHED!")
|
259
|
-
end
|
260
|
-
|
261
|
-
# Handle cleaning up this actor after it exits
|
262
|
-
def __cleanup(exit_event)
|
263
|
-
@_mailbox.shutdown
|
264
|
-
@_links.send_event exit_event
|
149
|
+
end
|
150
|
+
end # unexpected messages are ignored
|
265
151
|
end
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
152
|
+
end
|
153
|
+
|
154
|
+
# Handle exit events received by this actor
|
155
|
+
def handle_exit_event(exit_event)
|
156
|
+
klass = @subject.class
|
157
|
+
exit_handler = klass.exit_handler if klass.respond_to? :exit_handler
|
158
|
+
if exit_handler
|
159
|
+
return @subject.send(exit_handler, exit_event.actor, exit_event.reason)
|
272
160
|
end
|
161
|
+
|
162
|
+
# Reraise exceptions from linked actors
|
163
|
+
# If no reason is given, actor terminated cleanly
|
164
|
+
raise exit_event.reason if exit_event.reason
|
165
|
+
end
|
166
|
+
|
167
|
+
# Handle any exceptions that occur within a running actor
|
168
|
+
def handle_crash(exception)
|
169
|
+
log_error(exception)
|
170
|
+
cleanup ExitEvent.new(@proxy, exception)
|
171
|
+
rescue Exception => handler_exception
|
172
|
+
log_error(handler_exception, "ERROR HANDLER CRASHED!")
|
273
173
|
end
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
174
|
+
|
175
|
+
# Handle cleaning up this actor after it exits
|
176
|
+
def cleanup(exit_event)
|
177
|
+
@mailbox.shutdown
|
178
|
+
@links.send_event exit_event
|
179
|
+
end
|
180
|
+
|
181
|
+
# Log errors when an actor crashes
|
182
|
+
def log_error(ex, message = "#{@subject.class} crashed!")
|
183
|
+
message << "\n#{ex.class}: #{ex.to_s}\n"
|
184
|
+
message << ex.backtrace.join("\n")
|
185
|
+
Celluloid.logger.error message if Celluloid.logger
|
280
186
|
end
|
281
187
|
end
|
282
|
-
end
|
188
|
+
end
|