celluloid 0.12.0 → 0.13.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.
Files changed (40) hide show
  1. data/README.md +36 -23
  2. data/lib/celluloid/actor.rb +74 -47
  3. data/lib/celluloid/autostart.rb +16 -0
  4. data/lib/celluloid/calls.rb +52 -52
  5. data/lib/celluloid/condition.rb +70 -0
  6. data/lib/celluloid/core_ext.rb +1 -1
  7. data/lib/celluloid/cpu_counter.rb +7 -1
  8. data/lib/celluloid/fsm.rb +49 -16
  9. data/lib/celluloid/future.rb +5 -2
  10. data/lib/celluloid/internal_pool.rb +62 -44
  11. data/lib/celluloid/legacy.rb +46 -0
  12. data/lib/celluloid/links.rb +0 -5
  13. data/lib/celluloid/logger.rb +14 -4
  14. data/lib/celluloid/logging/incident.rb +21 -0
  15. data/lib/celluloid/logging/incident_logger.rb +129 -0
  16. data/lib/celluloid/logging/incident_reporter.rb +48 -0
  17. data/lib/celluloid/logging/log_event.rb +20 -0
  18. data/lib/celluloid/logging/ring_buffer.rb +65 -0
  19. data/lib/celluloid/logging.rb +5 -0
  20. data/lib/celluloid/mailbox.rb +2 -1
  21. data/lib/celluloid/method.rb +5 -0
  22. data/lib/celluloid/pool_manager.rb +21 -8
  23. data/lib/celluloid/proxies/actor_proxy.rb +11 -11
  24. data/lib/celluloid/proxies/async_proxy.rb +5 -1
  25. data/lib/celluloid/responses.rb +8 -6
  26. data/lib/celluloid/stack_dump.rb +94 -0
  27. data/lib/celluloid/supervision_group.rb +5 -3
  28. data/lib/celluloid/system_events.rb +11 -0
  29. data/lib/celluloid/tasks/task_fiber.rb +17 -9
  30. data/lib/celluloid/tasks/task_thread.rb +24 -14
  31. data/lib/celluloid/tasks.rb +59 -0
  32. data/lib/celluloid/thread_handle.rb +10 -1
  33. data/lib/celluloid/version.rb +1 -1
  34. data/lib/celluloid.rb +146 -87
  35. data/spec/support/actor_examples.rb +242 -114
  36. data/spec/support/example_actor_class.rb +17 -5
  37. data/spec/support/task_examples.rb +11 -2
  38. metadata +58 -14
  39. data/lib/celluloid/boot.rb +0 -9
  40. data/lib/celluloid/task.rb +0 -22
data/lib/celluloid.rb CHANGED
@@ -6,11 +6,14 @@ require 'set'
6
6
  module Celluloid
7
7
  extend self # expose all instance methods as singleton methods
8
8
 
9
- SHUTDOWN_TIMEOUT = 120 # How long actors have to terminate
9
+ # Warning message added to Celluloid objects accessed outside their actors
10
+ BARE_OBJECT_WARNING_MESSAGE = "WARNING: BARE CELLULOID OBJECT "
10
11
 
11
12
  class << self
12
- attr_accessor :logger # Thread-safe logger class
13
- attr_accessor :task_class # Default task type to use
13
+ attr_accessor :internal_pool # Internal thread pool
14
+ attr_accessor :logger # Thread-safe logger class
15
+ attr_accessor :task_class # Default task type to use
16
+ attr_accessor :shutdown_timeout # How long actors have to terminate
14
17
 
15
18
  def included(klass)
16
19
  klass.send :extend, ClassMethods
@@ -19,7 +22,7 @@ module Celluloid
19
22
 
20
23
  # Are we currently inside of an actor?
21
24
  def actor?
22
- !!Thread.current[:actor]
25
+ !!Thread.current[:celluloid_actor]
23
26
  end
24
27
 
25
28
  # Generate a Universally Unique Identifier
@@ -29,21 +32,44 @@ module Celluloid
29
32
 
30
33
  # Obtain the number of CPUs in the system
31
34
  def cores
32
- CPUCounter.cores
35
+ CPUCounter.cores
33
36
  end
34
37
  alias_method :cpus, :cores
35
38
  alias_method :ncpus, :cores
36
39
 
40
+ # Perform a stack dump of all actors to the given output object
41
+ def stack_dump(output = STDERR)
42
+ Celluloid::StackDump.new.dump(output)
43
+ end
44
+ alias_method :dump, :stack_dump
45
+
37
46
  # Define an exception handler for actor crashes
38
47
  def exception_handler(&block)
39
48
  Logger.exception_handler(&block)
40
49
  end
41
50
 
51
+ def suspend(status, waiter)
52
+ task = Thread.current[:celluloid_task]
53
+ if task && !Celluloid.exclusive?
54
+ waiter.before_suspend(task) if waiter.respond_to?(:before_suspend)
55
+ Task.suspend(status)
56
+ else
57
+ waiter.wait
58
+ end
59
+ end
60
+
61
+ # Launch default services
62
+ # FIXME: We should set up the supervision hierarchy here
63
+ def boot
64
+ Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
65
+ Celluloid::IncidentReporter.supervise_as :default_incident_reporter, STDERR
66
+ end
67
+
42
68
  # Shut down all running actors
43
69
  def shutdown
44
- Timeout.timeout(SHUTDOWN_TIMEOUT) do
70
+ Timeout.timeout(shutdown_timeout) do
45
71
  actors = Actor.all
46
- Logger.info "Terminating #{actors.size} actors..." if actors.size > 0
72
+ Logger.debug "Terminating #{actors.size} actors..." if actors.size > 0
47
73
 
48
74
  # Attempt to shut down the supervision tree, if available
49
75
  Supervisor.root.terminate if Supervisor.root
@@ -63,14 +89,13 @@ module Celluloid
63
89
  end
64
90
  end
65
91
 
66
- Logger.info "Shutdown completed cleanly"
92
+ Logger.debug "Shutdown completed cleanly"
67
93
  end
94
+ rescue Timeout::Error => ex
95
+ Logger.error("Couldn't cleanly terminate all actors in #{shutdown_timeout} seconds!")
68
96
  end
69
97
  end
70
98
 
71
- # Terminate all actors at exit
72
- at_exit { shutdown }
73
-
74
99
  # Class methods added to classes which include Celluloid
75
100
  module ClassMethods
76
101
  # Create a new actor
@@ -120,26 +145,55 @@ module Celluloid
120
145
 
121
146
  # Run an actor in the foreground
122
147
  def run(*args, &block)
123
- new(*args, &block).join
148
+ Actor.join(new(*args, &block))
124
149
  end
125
150
 
126
151
  # Trap errors from actors we're linked to when they exit
127
- def trap_exit(callback)
128
- @exit_handler = callback.to_sym
152
+ def exit_handler(callback = nil)
153
+ if callback
154
+ @exit_handler = callback.to_sym
155
+ elsif defined?(@exit_handler)
156
+ @exit_handler
157
+ elsif superclass.respond_to? :exit_handler
158
+ superclass.exit_handler
159
+ end
129
160
  end
130
-
131
- # Configure a custom mailbox factory
132
- def use_mailbox(klass = nil, &block)
133
- if block
134
- @mailbox_factory = block
135
- else
136
- mailbox_class(klass)
161
+ alias_method :trap_exit, :exit_handler
162
+
163
+ # Define a callback to run when the actor is finalized.
164
+ def finalizer(callback = nil)
165
+ if callback
166
+ @finalizer = callback.to_sym
167
+ elsif defined?(@finalizer)
168
+ @finalizer
169
+ elsif superclass.respond_to? :finalizer
170
+ superclass.finalizer
137
171
  end
138
172
  end
139
173
 
140
174
  # Define the mailbox class for this class
141
- def mailbox_class(klass)
142
- @mailbox_factory = proc { klass.new }
175
+ def mailbox_class(klass = nil)
176
+ if klass
177
+ @mailbox_class = klass
178
+ elsif defined?(@mailbox_class)
179
+ @mailbox_class
180
+ elsif superclass.respond_to? :mailbox_class
181
+ superclass.mailbox_class
182
+ else
183
+ Celluloid::Mailbox
184
+ end
185
+ end
186
+
187
+ def proxy_class(klass = nil)
188
+ if klass
189
+ @proxy_class = klass
190
+ elsif defined?(@proxy_class)
191
+ @proxy_class
192
+ elsif superclass.respond_to? :proxy_class
193
+ superclass.proxy_class
194
+ else
195
+ Celluloid::ActorProxy
196
+ end
143
197
  end
144
198
 
145
199
  # Define the default task type for this class
@@ -165,24 +219,20 @@ module Celluloid
165
219
  end
166
220
  end
167
221
 
168
- # Create a mailbox for this actor
169
- def mailbox_factory
170
- if defined?(@mailbox_factory)
171
- @mailbox_factory.call
172
- elsif superclass.respond_to? :mailbox_factory
173
- superclass.mailbox_factory
174
- else
175
- Mailbox.new
176
- end
222
+ # Mark methods as running blocks on the receiver
223
+ def execute_block_on_receiver(*methods)
224
+ # A noop method in preparation
225
+ # See https://github.com/celluloid/celluloid/pull/55
177
226
  end
178
227
 
179
228
  # Configuration options for Actor#new
180
229
  def actor_options
181
230
  {
182
- :mailbox => mailbox_factory,
183
- :exit_handler => @exit_handler,
184
- :exclusive_methods => @exclusive_methods,
185
- :task_class => task_class
231
+ :mailbox => mailbox_class.new,
232
+ :proxy_class => proxy_class,
233
+ :task_class => task_class,
234
+ :exit_handler => exit_handler,
235
+ :exclusive_methods => defined?(@exclusive_methods) ? @exclusive_methods : nil
186
236
  }
187
237
  end
188
238
 
@@ -194,44 +244,45 @@ module Celluloid
194
244
  # These are methods we don't want added to the Celluloid singleton but to be
195
245
  # defined on all classes that use Celluloid
196
246
  module InstanceMethods
197
- # Obtain the Ruby object the actor is wrapping. This should ONLY be used
198
- # for a limited set of use cases like runtime metaprogramming. Interacting
199
- # directly with the wrapped object foregoes any kind of thread safety that
247
+ # Obtain the bare Ruby object the actor is wrapping. This is useful for
248
+ # only a limited set of use cases like runtime metaprogramming. Interacting
249
+ # directly with the bare object foregoes any kind of thread safety that
200
250
  # Celluloid would ordinarily provide you, and the object is guaranteed to
201
251
  # be shared with at least the actor thread. Tread carefully.
202
- def wrapped_object; self; end
252
+ #
253
+ # Bare objects can be identified via #inspect output:
254
+ #
255
+ # >> actor
256
+ # => #<Celluloid::Actor(Foo:0x3fefcb77c194)>
257
+ # >> actor.bare_object
258
+ # => #<WARNING: BARE CELLULOID OBJECT (Foo:0x3fefcb77c194)>
259
+ #
260
+ def bare_object; self; end
261
+ alias_method :wrapped_object, :bare_object
262
+
263
+ # Are we being invoked in a different thread from our owner?
264
+ def leaked?
265
+ @celluloid_owner != Thread.current[:celluloid_actor]
266
+ end
203
267
 
204
268
  def inspect
205
- str = "#<Celluloid::Actor(#{self.class}:0x#{object_id.to_s(16)})"
206
- ivars = instance_variables.map do |ivar|
207
- "#{ivar}=#{instance_variable_get(ivar).inspect}"
269
+ str = "#<"
270
+
271
+ if leaked?
272
+ str << Celluloid::BARE_OBJECT_WARNING_MESSAGE
273
+ else
274
+ str << "Celluloid::ActorProxy"
208
275
  end
209
276
 
210
- str << " " << ivars.join(' ') unless ivars.empty?
211
- str << ">"
212
- end
213
-
214
- # Process async calls via method_missing
215
- def method_missing(meth, *args, &block)
216
- # bang methods are async calls
217
- if meth.to_s.match(/!$/)
218
- unbanged_meth = meth.to_s.sub(/!$/, '')
219
- args.unshift unbanged_meth
220
-
221
- call = AsyncCall.new(:__send__, args, block)
222
- begin
223
- Thread.current[:actor].mailbox << call
224
- rescue MailboxError
225
- # Silently swallow asynchronous calls to dead actors. There's no way
226
- # to reliably generate DeadActorErrors for async calls, so users of
227
- # async calls should find other ways to deal with actors dying
228
- # during an async call (i.e. linking/supervisors)
229
- end
277
+ str << "(#{self.class}:0x#{object_id.to_s(16)})"
278
+ str << " " unless instance_variables.empty?
230
279
 
231
- return
280
+ instance_variables.each do |ivar|
281
+ next if ivar == Celluloid::OWNER_IVAR
282
+ str << "#{ivar}=#{instance_variable_get(ivar).inspect} "
232
283
  end
233
284
 
234
- super
285
+ str.sub!(/\s$/, '>')
235
286
  end
236
287
  end
237
288
 
@@ -240,11 +291,6 @@ module Celluloid
240
291
  # directly inside of all classes that include Celluloid
241
292
  #
242
293
 
243
- # Is this actor alive?
244
- def alive?
245
- Thread.current[:actor].alive?
246
- end
247
-
248
294
  # Raise an exception in caller context, but stay running
249
295
  def abort(cause)
250
296
  cause = case cause
@@ -257,17 +303,17 @@ module Celluloid
257
303
 
258
304
  # Terminate this actor
259
305
  def terminate
260
- Thread.current[:actor].terminate
306
+ Thread.current[:celluloid_actor].terminate
261
307
  end
262
308
 
263
309
  # Send a signal with the given name to all waiting methods
264
310
  def signal(name, value = nil)
265
- Thread.current[:actor].signal name, value
311
+ Thread.current[:celluloid_actor].signal name, value
266
312
  end
267
313
 
268
314
  # Wait for the given signal
269
315
  def wait(name)
270
- Thread.current[:actor].wait name
316
+ Thread.current[:celluloid_actor].wait name
271
317
  end
272
318
 
273
319
  # Obtain the current_actor
@@ -275,6 +321,11 @@ module Celluloid
275
321
  Actor.current
276
322
  end
277
323
 
324
+ # Obtain the UUID of the current call chain
325
+ def call_chain_id
326
+ Thread.current[:celluloid_chain_id]
327
+ end
328
+
278
329
  # Obtain the name of the current actor
279
330
  def name
280
331
  Actor.name
@@ -282,12 +333,12 @@ module Celluloid
282
333
 
283
334
  # Obtain the running tasks for this actor
284
335
  def tasks
285
- Thread.current[:actor].tasks.to_a
336
+ Thread.current[:celluloid_actor].tasks.to_a
286
337
  end
287
338
 
288
339
  # Obtain the Celluloid::Links for this actor
289
340
  def links
290
- Thread.current[:actor].links
341
+ Thread.current[:celluloid_actor].links
291
342
  end
292
343
 
293
344
  # Watch for exit events from another actor
@@ -322,7 +373,7 @@ module Celluloid
322
373
 
323
374
  # Receive an asynchronous message via the actor protocol
324
375
  def receive(timeout = nil, &block)
325
- actor = Thread.current[:actor]
376
+ actor = Thread.current[:celluloid_actor]
326
377
  if actor
327
378
  actor.receive(timeout, &block)
328
379
  else
@@ -332,7 +383,7 @@ module Celluloid
332
383
 
333
384
  # Sleep letting the actor continue processing messages
334
385
  def sleep(interval)
335
- actor = Thread.current[:actor]
386
+ actor = Thread.current[:celluloid_actor]
336
387
  if actor
337
388
  actor.sleep(interval)
338
389
  else
@@ -343,23 +394,23 @@ module Celluloid
343
394
  # Run given block in an exclusive mode: all synchronous calls block the whole
344
395
  # actor, not only current message processing.
345
396
  def exclusive(&block)
346
- Thread.current[:actor].exclusive(&block)
397
+ Thread.current[:celluloid_actor].exclusive(&block)
347
398
  end
348
399
 
349
400
  # Are we currently exclusive
350
401
  def exclusive?
351
- actor = Thread.current[:actor]
402
+ actor = Thread.current[:celluloid_actor]
352
403
  actor && actor.exclusive?
353
404
  end
354
405
 
355
406
  # Call a block after a given interval, returning a Celluloid::Timer object
356
407
  def after(interval, &block)
357
- Thread.current[:actor].after(interval, &block)
408
+ Thread.current[:celluloid_actor].after(interval, &block)
358
409
  end
359
410
 
360
411
  # Call a block every given interval, returning a Celluloid::Timer object
361
412
  def every(interval, &block)
362
- Thread.current[:actor].every(interval, &block)
413
+ Thread.current[:celluloid_actor].every(interval, &block)
363
414
  end
364
415
 
365
416
  # Perform a blocking or computationally intensive action inside an
@@ -374,18 +425,18 @@ module Celluloid
374
425
  # Handle async calls within an actor itself
375
426
  def async(meth = nil, *args, &block)
376
427
  if meth
377
- Actor.async Thread.current[:actor].mailbox, meth, *args, &block
428
+ Actor.async Thread.current[:celluloid_actor].mailbox, meth, *args, &block
378
429
  else
379
- Thread.current[:actor].proxy.async
430
+ Thread.current[:celluloid_actor].proxy.async
380
431
  end
381
432
  end
382
433
 
383
434
  # Handle calls to future within an actor itself
384
435
  def future(meth = nil, *args, &block)
385
436
  if meth
386
- Actor.future Thread.current[:actor].mailbox, meth, *args, &block
437
+ Actor.future Thread.current[:celluloid_actor].mailbox, meth, *args, &block
387
438
  else
388
- Thread.current[:actor].proxy.future
439
+ Thread.current[:celluloid_actor].proxy.future
389
440
  end
390
441
  end
391
442
  end
@@ -393,6 +444,7 @@ end
393
444
  require 'celluloid/version'
394
445
 
395
446
  require 'celluloid/calls'
447
+ require 'celluloid/condition'
396
448
  require 'celluloid/core_ext'
397
449
  require 'celluloid/cpu_counter'
398
450
  require 'celluloid/fiber'
@@ -406,8 +458,9 @@ require 'celluloid/receivers'
406
458
  require 'celluloid/registry'
407
459
  require 'celluloid/responses'
408
460
  require 'celluloid/signals'
461
+ require 'celluloid/stack_dump'
409
462
  require 'celluloid/system_events'
410
- require 'celluloid/task'
463
+ require 'celluloid/tasks'
411
464
  require 'celluloid/thread_handle'
412
465
  require 'celluloid/uuid'
413
466
 
@@ -422,5 +475,11 @@ require 'celluloid/pool_manager'
422
475
  require 'celluloid/supervision_group'
423
476
  require 'celluloid/supervisor'
424
477
  require 'celluloid/notifications'
478
+ require 'celluloid/logging'
479
+
480
+ require 'celluloid/legacy' unless defined?(CELLULOID_FUTURE)
425
481
 
426
- require 'celluloid/boot'
482
+ # Configure default systemwide settings
483
+ Celluloid.task_class = Celluloid::TaskFiber
484
+ Celluloid.logger = Logger.new(STDERR)
485
+ Celluloid.shutdown_timeout = 10