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.
@@ -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