celluloid 0.2.2 → 0.5.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 +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
|