celluloid 0.2.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,24 +1,150 @@
1
1
  require 'logger'
2
2
 
3
- module Celluloid
4
- @@logger_lock = Mutex.new
5
- @@logger = Logger.new STDERR
3
+ module Celluloid
4
+ @logger = Logger.new STDERR
6
5
 
7
- def self.logger
8
- @@logger_lock.synchronize { @@logger }
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 self.logger=(logger)
12
- @@logger_lock.synchronize { @@logger = logger }
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
- def self.included(klass)
16
- klass.send :include, Actor
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'
@@ -3,8 +3,10 @@ require 'thread'
3
3
  begin
4
4
  require 'fiber'
5
5
  rescue LoadError => ex
6
- # If we're on Rubinius, we can still work in 1.8 mode
7
- if defined? Rubinius
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
- # Raised when trying to do Actor-like things outside Actor-space
17
+ # Don't do Actor-like things outside Actor scope
16
18
  class NotActorError < StandardError; end
17
-
18
- # Raised when we're asked to do something to a dead actor
19
+
20
+ # Trying to do something to a dead actor
19
21
  class DeadActorError < StandardError; end
20
-
21
- # Raised when the caller makes an error that shouldn't crash this actor
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. The implementation is inspired by Erlang's gen_server
47
- module Actor
48
- # Methods added to classes which include Celluloid
49
- module ClassMethods
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
- actor = allocate
65
- proxy = actor.__start_actor
66
- current_actor.link actor
67
- proxy.send(:initialize, *args, &block)
68
-
69
- proxy
70
- end
71
- alias_method :new_link, :spawn_link
72
-
73
- # Create a supervisor which ensures an instance of an actor will restart
74
- # an actor if it fails
75
- def supervise(*args, &block)
76
- Celluloid::Supervisor.supervise(self, *args, &block)
77
- end
78
-
79
- # Create a supervisor which ensures an instance of an actor will restart
80
- # an actor if it fails, and keep the actor registered under a given name
81
- def supervise_as(name, *args, &block)
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
- begin
152
- @_mailbox << AsyncCall.new(@_mailbox, unbanged_meth, args, block)
153
- rescue MailboxError
154
- # Silently swallow asynchronous calls to dead actors. There's no way
155
- # to reliably generate DeadActorErrors for async calls, so users of
156
- # async calls should find other ways to deal with actors dying
157
- # during an async call (i.e. linking/supervisors)
158
- end
159
-
160
- return # casts are async and return immediately
161
- end
162
-
163
- super
164
- end
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
- # Internal methods not intended as part of the public API
168
- module InternalMethods
169
- # Actor-specific initialization and startup
170
- def __start_actor(*args, &block)
171
- @_mailbox = Mailbox.new
172
- @_links = Links.new
173
- @_signals = Signals.new
174
- @_proxy = ActorProxy.new(self, @_mailbox)
175
- @_running = true
176
-
177
- @_thread = Thread.new do
178
- __init_thread
179
- __run_actor
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
- @_proxy
183
- end
184
-
185
- # Configure thread locals for the running thread
186
- def __init_thread
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
- case message
222
- when Call
223
- fiber = Fiber.new do
224
- __init_thread
225
- message.dispatch(self)
226
- end
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
- when Response
231
- fiber = pending_calls.delete(message.call)
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
- # Log errors when an actor crashes
268
- def __log_error(ex, message = "#{self.class} crashed!")
269
- message << "\n#{ex.class}: #{ex.to_s}\n"
270
- message << ex.backtrace.join("\n")
271
- Celluloid.logger.error message if Celluloid.logger
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
- def self.included(klass)
276
- klass.extend ClassMethods
277
- klass.send :include, InstanceMethods
278
- klass.send :include, InternalMethods
279
- klass.send :include, Linking
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