celluloid 0.13.0 → 0.14.1

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. checksums.yaml +7 -0
  2. data/README.md +1 -2
  3. data/lib/celluloid/actor.rb +33 -30
  4. data/lib/celluloid/autostart.rb +0 -13
  5. data/lib/celluloid/calls.rb +71 -23
  6. data/lib/celluloid/condition.rb +32 -28
  7. data/lib/celluloid/core_ext.rb +3 -14
  8. data/lib/celluloid/cpu_counter.rb +1 -1
  9. data/lib/celluloid/evented_mailbox.rb +82 -0
  10. data/lib/celluloid/fsm.rb +2 -0
  11. data/lib/celluloid/future.rb +22 -31
  12. data/lib/celluloid/internal_pool.rb +13 -0
  13. data/lib/celluloid/legacy.rb +14 -13
  14. data/lib/celluloid/logging/incident_logger.rb +2 -2
  15. data/lib/celluloid/mailbox.rb +16 -0
  16. data/lib/celluloid/method.rb +7 -7
  17. data/lib/celluloid/notifications.rb +1 -1
  18. data/lib/celluloid/proxies/abstract_proxy.rb +1 -1
  19. data/lib/celluloid/proxies/actor_proxy.rb +23 -27
  20. data/lib/celluloid/proxies/async_proxy.rb +18 -6
  21. data/lib/celluloid/proxies/block_proxy.rb +29 -0
  22. data/lib/celluloid/proxies/future_proxy.rb +17 -3
  23. data/lib/celluloid/proxies/sync_proxy.rb +31 -0
  24. data/lib/celluloid/receivers.rb +1 -1
  25. data/lib/celluloid/responses.rb +13 -1
  26. data/lib/celluloid/stack_dump.rb +8 -5
  27. data/lib/celluloid/supervision_group.rb +10 -10
  28. data/lib/celluloid/system_events.rb +1 -0
  29. data/lib/celluloid/tasks/task_fiber.rb +9 -47
  30. data/lib/celluloid/tasks/task_thread.rb +9 -44
  31. data/lib/celluloid/tasks.rb +63 -2
  32. data/lib/celluloid/thread.rb +38 -0
  33. data/lib/celluloid/thread_handle.rb +5 -3
  34. data/lib/celluloid/version.rb +1 -1
  35. data/lib/celluloid.rb +84 -32
  36. data/spec/support/actor_examples.rb +92 -53
  37. data/spec/support/example_actor_class.rb +7 -1
  38. data/spec/support/mailbox_examples.rb +29 -3
  39. data/spec/support/task_examples.rb +11 -9
  40. metadata +21 -29
@@ -1,62 +1,38 @@
1
1
  module Celluloid
2
2
  # Tasks with a Thread backend
3
3
  class TaskThread < Task
4
- attr_reader :type, :status
5
-
6
4
  # Run the given block within a task
7
5
  def initialize(type)
8
- super
9
-
10
6
  @resume_queue = Queue.new
11
7
  @exception_queue = Queue.new
12
8
  @yield_mutex = Mutex.new
13
9
  @yield_cond = ConditionVariable.new
14
10
 
15
- actor = Thread.current[:celluloid_actor]
16
- mailbox = Thread.current[:celluloid_mailbox]
17
- chain_id = Thread.current[:celluloid_chain_id]
18
-
19
- raise NotActorError, "can't create tasks outside of actors" unless actor
11
+ super
12
+ end
20
13
 
14
+ def create
21
15
  @thread = Celluloid.internal_pool.get do
16
+ Thread.current.role = :task
22
17
  begin
23
18
  ex = @resume_queue.pop
24
19
  raise ex if ex.is_a?(Task::TerminatedError)
25
20
 
26
- @status = :running
27
- Thread.current[:celluloid_actor] = actor
28
- Thread.current[:celluloid_mailbox] = mailbox
29
- Thread.current[:celluloid_task] = self
30
- Thread.current[:celluloid_chain_id] = chain_id
31
-
32
- actor.tasks << self
33
21
  yield
34
- rescue Task::TerminatedError
35
- # Task was explicitly terminated
36
22
  rescue Exception => ex
37
23
  @exception_queue << ex
38
24
  ensure
39
- @status = :dead
40
- actor.tasks.delete self
41
25
  @yield_cond.signal
42
26
  end
43
27
  end
44
28
  end
45
29
 
46
- # Suspend the current task, changing the status to the given argument
47
- def suspend(status)
48
- @status = status
30
+ def signal
49
31
  @yield_cond.signal
50
- value = @resume_queue.pop
51
-
52
- raise value if value.is_a?(Task::TerminatedError)
53
- @status = :running
54
-
55
- value
32
+ @resume_queue.pop
56
33
  end
57
34
 
58
- # Resume a suspended task, giving it a value to return if needed
59
- def resume(value = nil)
35
+ def deliver(value)
60
36
  raise DeadTaskError, "cannot resume a dead task" unless @thread.alive?
61
37
 
62
38
  @yield_mutex.synchronize do
@@ -66,23 +42,12 @@ module Celluloid
66
42
  raise @exception_queue.pop
67
43
  end
68
44
  end
69
-
70
- nil
71
45
  rescue ThreadError
72
46
  raise DeadTaskError, "cannot resume a dead task"
73
47
  end
74
48
 
75
- # Terminate this task
76
- def terminate
77
- resume Task::TerminatedError.new("task was terminated") if @thread.alive?
78
- end
79
-
80
- # Is the current task still running?
81
- def running?; @status != :dead; end
82
-
83
- # Nicer string inspect for tasks
84
- def inspect
85
- "<Celluloid::TaskThread:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
49
+ def backtrace
50
+ @thread.backtrace
86
51
  end
87
52
  end
88
53
  end
@@ -19,10 +19,71 @@ module Celluloid
19
19
  Task.current.suspend(status)
20
20
  end
21
21
 
22
+ attr_reader :type, :status
23
+
22
24
  # Create a new task
23
25
  def initialize(type)
24
26
  @type = type
25
27
  @status = :new
28
+
29
+ actor = Thread.current[:celluloid_actor]
30
+ chain_id = Thread.current[:celluloid_chain_id]
31
+
32
+ raise NotActorError, "can't create tasks outside of actors" unless actor
33
+
34
+ create do
35
+ begin
36
+ @status = :running
37
+ actor.setup_thread
38
+ Thread.current[:celluloid_task] = self
39
+ Thread.current[:celluloid_chain_id] = chain_id
40
+
41
+ actor.tasks << self
42
+ yield
43
+ rescue Task::TerminatedError
44
+ # Task was explicitly terminated
45
+ ensure
46
+ @status = :dead
47
+ actor.tasks.delete self
48
+ end
49
+ end
50
+ end
51
+
52
+ def create(&block)
53
+ raise "Implement #{self.class}#create"
54
+ end
55
+
56
+ # Suspend the current task, changing the status to the given argument
57
+ def suspend(status)
58
+ @status = status
59
+ value = signal
60
+
61
+ raise value if value.is_a?(Task::TerminatedError)
62
+ @status = :running
63
+
64
+ value
65
+ end
66
+
67
+ # Resume a suspended task, giving it a value to return if needed
68
+ def resume(value = nil)
69
+ deliver(value)
70
+ nil
71
+ end
72
+
73
+ # Terminate this task
74
+ def terminate
75
+ resume Task::TerminatedError.new("task was terminated") if running?
76
+ end
77
+
78
+ def backtrace
79
+ end
80
+
81
+ # Is the current task still running?
82
+ def running?; @status != :dead; end
83
+
84
+ # Nicer string inspect for tasks
85
+ def inspect
86
+ "#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
26
87
  end
27
88
  end
28
89
 
@@ -42,7 +103,7 @@ module Celluloid
42
103
  end
43
104
 
44
105
  def each(&blk)
45
- @tasks.each &blk
106
+ @tasks.each(&blk)
46
107
  end
47
108
 
48
109
  def first
@@ -56,4 +117,4 @@ module Celluloid
56
117
  end
57
118
 
58
119
  require 'celluloid/tasks/task_fiber'
59
- require 'celluloid/tasks/task_thread'
120
+ require 'celluloid/tasks/task_thread'
@@ -0,0 +1,38 @@
1
+ require 'celluloid/fiber'
2
+
3
+ module Celluloid
4
+ class Thread < ::Thread
5
+ def celluloid?
6
+ true
7
+ end
8
+
9
+ # Obtain the role of this thread
10
+ def role
11
+ self[:celluloid_role]
12
+ end
13
+
14
+ def role=(role)
15
+ self[:celluloid_role] = role
16
+ end
17
+
18
+ # Obtain the Celluloid::Actor object for this thread
19
+ def actor
20
+ self[:celluloid_actor]
21
+ end
22
+
23
+ # Obtain the Celluloid task object for this thread
24
+ def task
25
+ self[:celluloid_task]
26
+ end
27
+
28
+ # Obtain the Celluloid mailbox for this thread
29
+ def mailbox
30
+ self[:celluloid_mailbox]
31
+ end
32
+
33
+ # Obtain the call chain ID for this thread
34
+ def call_chain_id
35
+ self[:celluloid_chain_id]
36
+ end
37
+ end
38
+ end
@@ -3,11 +3,12 @@ module Celluloid
3
3
  # accidentally do things to threads which have been returned to the pool,
4
4
  # such as, say, killing them
5
5
  class ThreadHandle
6
- def initialize
6
+ def initialize(role = nil)
7
7
  @mutex = Mutex.new
8
8
  @join = ConditionVariable.new
9
9
 
10
10
  @thread = Celluloid.internal_pool.get do
11
+ Thread.current.role = role
11
12
  begin
12
13
  yield
13
14
  ensure
@@ -31,8 +32,9 @@ module Celluloid
31
32
  end
32
33
 
33
34
  # Join to a running thread, blocking until it terminates
34
- def join
35
- @mutex.synchronize { @join.wait(@mutex) if @thread }
35
+ def join(limit = nil)
36
+ raise ThreadError, "Target thread must not be current thread" if @thread == Thread.current
37
+ @mutex.synchronize { @join.wait(@mutex, limit) if @thread }
36
38
  self
37
39
  end
38
40
 
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.13.0'
2
+ VERSION = '0.14.1'
3
3
  def self.version; VERSION; end
4
4
  end
data/lib/celluloid.rb CHANGED
@@ -25,6 +25,11 @@ module Celluloid
25
25
  !!Thread.current[:celluloid_actor]
26
26
  end
27
27
 
28
+ # Retrieve the mailbox for the current thread or lazily initialize it
29
+ def mailbox
30
+ Thread.current[:celluloid_mailbox] ||= Celluloid::Mailbox.new
31
+ end
32
+
28
33
  # Generate a Universally Unique Identifier
29
34
  def uuid
30
35
  UUID.generate
@@ -61,13 +66,33 @@ module Celluloid
61
66
  # Launch default services
62
67
  # FIXME: We should set up the supervision hierarchy here
63
68
  def boot
69
+ internal_pool.reset
64
70
  Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
65
71
  Celluloid::IncidentReporter.supervise_as :default_incident_reporter, STDERR
66
72
  end
67
73
 
74
+ def register_shutdown
75
+ return if @shutdown_registered
76
+ # Terminate all actors at exit
77
+ at_exit do
78
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION >= "1.9"
79
+ # workaround for MRI bug losing exit status in at_exit block
80
+ # http://bugs.ruby-lang.org/issues/5218
81
+ exit_status = $!.status if $!.is_a?(SystemExit)
82
+ Celluloid.shutdown
83
+ exit exit_status if exit_status
84
+ else
85
+ Celluloid.shutdown
86
+ end
87
+ end
88
+ @shutdown_registered = true
89
+ end
90
+
68
91
  # Shut down all running actors
69
92
  def shutdown
70
93
  Timeout.timeout(shutdown_timeout) do
94
+ internal_pool.shutdown
95
+
71
96
  actors = Actor.all
72
97
  Logger.debug "Terminating #{actors.size} actors..." if actors.size > 0
73
98
 
@@ -75,14 +100,14 @@ module Celluloid
75
100
  Supervisor.root.terminate if Supervisor.root
76
101
 
77
102
  # Actors cannot self-terminate, you must do it for them
78
- Actor.all.each do |actor|
103
+ actors.each do |actor|
79
104
  begin
80
105
  actor.terminate!
81
106
  rescue DeadActorError, MailboxError
82
107
  end
83
108
  end
84
109
 
85
- Actor.all.each do |actor|
110
+ actors.each do |actor|
86
111
  begin
87
112
  Actor.join(actor)
88
113
  rescue DeadActorError, MailboxError
@@ -91,7 +116,7 @@ module Celluloid
91
116
 
92
117
  Logger.debug "Shutdown completed cleanly"
93
118
  end
94
- rescue Timeout::Error => ex
119
+ rescue Timeout::Error
95
120
  Logger.error("Couldn't cleanly terminate all actors in #{shutdown_timeout} seconds!")
96
121
  end
97
122
  end
@@ -174,13 +199,9 @@ module Celluloid
174
199
  # Define the mailbox class for this class
175
200
  def mailbox_class(klass = nil)
176
201
  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
202
+ mailbox.class = klass
182
203
  else
183
- Celluloid::Mailbox
204
+ mailbox.class
184
205
  end
185
206
  end
186
207
 
@@ -213,7 +234,7 @@ module Celluloid
213
234
  def exclusive(*methods)
214
235
  if methods.empty?
215
236
  @exclusive_methods = :all
216
- elsif @exclusive_methods != :all
237
+ elsif !defined?(@exclusive_methods) || @exclusive_methods != :all
217
238
  @exclusive_methods ||= Set.new
218
239
  @exclusive_methods.merge methods.map(&:to_sym)
219
240
  end
@@ -221,24 +242,53 @@ module Celluloid
221
242
 
222
243
  # Mark methods as running blocks on the receiver
223
244
  def execute_block_on_receiver(*methods)
224
- # A noop method in preparation
225
- # See https://github.com/celluloid/celluloid/pull/55
245
+ receiver_block_executions.merge methods.map(&:to_sym)
246
+ end
247
+
248
+ def receiver_block_executions
249
+ @receiver_block_executions ||= Set.new([:after, :every, :receive])
226
250
  end
227
251
 
228
252
  # Configuration options for Actor#new
229
253
  def actor_options
230
254
  {
231
- :mailbox => mailbox_class.new,
255
+ :mailbox => mailbox.build,
232
256
  :proxy_class => proxy_class,
233
257
  :task_class => task_class,
234
258
  :exit_handler => exit_handler,
235
- :exclusive_methods => defined?(@exclusive_methods) ? @exclusive_methods : nil
259
+ :exclusive_methods => defined?(@exclusive_methods) ? @exclusive_methods : nil,
260
+ :receiver_block_executions => receiver_block_executions
236
261
  }
237
262
  end
238
263
 
239
264
  def ===(other)
240
265
  other.kind_of? self
241
266
  end
267
+
268
+ def mailbox
269
+ @mailbox_factory ||= MailboxFactory.new(self)
270
+ end
271
+
272
+ class MailboxFactory
273
+ attr_accessor :class, :max_size
274
+
275
+ def initialize(actor)
276
+ @actor = actor
277
+ @class = nil
278
+ @max_size = nil
279
+ end
280
+
281
+ def build
282
+ mailbox = mailbox_class.new
283
+ mailbox.max_size = @max_size
284
+ mailbox
285
+ end
286
+
287
+ private
288
+ def mailbox_class
289
+ @class || (@actor.superclass.respond_to?(:mailbox_class) && @actor.superclass.mailbox_class) || Celluloid::Mailbox
290
+ end
291
+ end
242
292
  end
243
293
 
244
294
  # These are methods we don't want added to the Celluloid singleton but to be
@@ -265,6 +315,16 @@ module Celluloid
265
315
  @celluloid_owner != Thread.current[:celluloid_actor]
266
316
  end
267
317
 
318
+ def tap
319
+ yield current_actor
320
+ current_actor
321
+ end
322
+
323
+ # Obtain the name of the current actor
324
+ def name
325
+ Actor.name
326
+ end
327
+
268
328
  def inspect
269
329
  str = "#<"
270
330
 
@@ -291,7 +351,7 @@ module Celluloid
291
351
  # directly inside of all classes that include Celluloid
292
352
  #
293
353
 
294
- # Raise an exception in caller context, but stay running
354
+ # Raise an exception in sender context, but stay running
295
355
  def abort(cause)
296
356
  cause = case cause
297
357
  when String then RuntimeError.new(cause)
@@ -326,11 +386,6 @@ module Celluloid
326
386
  Thread.current[:celluloid_chain_id]
327
387
  end
328
388
 
329
- # Obtain the name of the current actor
330
- def name
331
- Actor.name
332
- end
333
-
334
389
  # Obtain the running tasks for this actor
335
390
  def tasks
336
391
  Thread.current[:celluloid_actor].tasks.to_a
@@ -377,7 +432,7 @@ module Celluloid
377
432
  if actor
378
433
  actor.receive(timeout, &block)
379
434
  else
380
- Thread.mailbox.receive(timeout, &block)
435
+ Celluloid.mailbox.receive(timeout, &block)
381
436
  end
382
437
  end
383
438
 
@@ -414,7 +469,7 @@ module Celluloid
414
469
  end
415
470
 
416
471
  # Perform a blocking or computationally intensive action inside an
417
- # asynchronous thread pool, allowing the caller to continue processing other
472
+ # asynchronous thread pool, allowing the sender to continue processing other
418
473
  # messages in its mailbox in the meantime
419
474
  def defer(&block)
420
475
  # This implementation relies on the present implementation of
@@ -424,20 +479,12 @@ module Celluloid
424
479
 
425
480
  # Handle async calls within an actor itself
426
481
  def async(meth = nil, *args, &block)
427
- if meth
428
- Actor.async Thread.current[:celluloid_actor].mailbox, meth, *args, &block
429
- else
430
- Thread.current[:celluloid_actor].proxy.async
431
- end
482
+ Thread.current[:celluloid_actor].proxy.async meth, *args, &block
432
483
  end
433
484
 
434
485
  # Handle calls to future within an actor itself
435
486
  def future(meth = nil, *args, &block)
436
- if meth
437
- Actor.future Thread.current[:celluloid_actor].mailbox, meth, *args, &block
438
- else
439
- Thread.current[:celluloid_actor].proxy.future
440
- end
487
+ Thread.current[:celluloid_actor].proxy.future meth, *args, &block
441
488
  end
442
489
  end
443
490
 
@@ -445,6 +492,7 @@ require 'celluloid/version'
445
492
 
446
493
  require 'celluloid/calls'
447
494
  require 'celluloid/condition'
495
+ require 'celluloid/thread'
448
496
  require 'celluloid/core_ext'
449
497
  require 'celluloid/cpu_counter'
450
498
  require 'celluloid/fiber'
@@ -453,6 +501,7 @@ require 'celluloid/internal_pool'
453
501
  require 'celluloid/links'
454
502
  require 'celluloid/logger'
455
503
  require 'celluloid/mailbox'
504
+ require 'celluloid/evented_mailbox'
456
505
  require 'celluloid/method'
457
506
  require 'celluloid/receivers'
458
507
  require 'celluloid/registry'
@@ -465,9 +514,11 @@ require 'celluloid/thread_handle'
465
514
  require 'celluloid/uuid'
466
515
 
467
516
  require 'celluloid/proxies/abstract_proxy'
517
+ require 'celluloid/proxies/sync_proxy'
468
518
  require 'celluloid/proxies/actor_proxy'
469
519
  require 'celluloid/proxies/async_proxy'
470
520
  require 'celluloid/proxies/future_proxy'
521
+ require 'celluloid/proxies/block_proxy'
471
522
 
472
523
  require 'celluloid/actor'
473
524
  require 'celluloid/future'
@@ -483,3 +534,4 @@ require 'celluloid/legacy' unless defined?(CELLULOID_FUTURE)
483
534
  Celluloid.task_class = Celluloid::TaskFiber
484
535
  Celluloid.logger = Logger.new(STDERR)
485
536
  Celluloid.shutdown_timeout = 10
537
+ Celluloid.register_shutdown