celluloid 0.14.1 → 0.15.0.pre

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/lib/celluloid.rb +92 -108
  4. data/lib/celluloid/actor.rb +42 -64
  5. data/lib/celluloid/autostart.rb +1 -1
  6. data/lib/celluloid/call_chain.rb +13 -0
  7. data/lib/celluloid/calls.rb +5 -8
  8. data/lib/celluloid/condition.rb +8 -10
  9. data/lib/celluloid/cpu_counter.rb +1 -1
  10. data/lib/celluloid/evented_mailbox.rb +7 -10
  11. data/lib/celluloid/fsm.rb +1 -1
  12. data/lib/celluloid/future.rb +1 -2
  13. data/lib/celluloid/internal_pool.rb +77 -20
  14. data/lib/celluloid/legacy.rb +0 -38
  15. data/lib/celluloid/mailbox.rb +17 -10
  16. data/lib/celluloid/pool_manager.rb +1 -1
  17. data/lib/celluloid/properties.rb +24 -0
  18. data/lib/celluloid/proxies/abstract_proxy.rb +3 -0
  19. data/lib/celluloid/proxies/actor_proxy.rb +3 -32
  20. data/lib/celluloid/proxies/async_proxy.rb +4 -8
  21. data/lib/celluloid/proxies/future_proxy.rb +8 -6
  22. data/lib/celluloid/proxies/sync_proxy.rb +12 -7
  23. data/lib/celluloid/rspec.rb +3 -1
  24. data/lib/celluloid/signals.rb +7 -35
  25. data/lib/celluloid/stack_dump.rb +50 -37
  26. data/lib/celluloid/supervision_group.rb +5 -5
  27. data/lib/celluloid/task_set.rb +49 -0
  28. data/lib/celluloid/tasks.rb +67 -42
  29. data/lib/celluloid/tasks/task_fiber.rb +3 -1
  30. data/lib/celluloid/tasks/task_thread.rb +2 -3
  31. data/lib/celluloid/thread.rb +2 -0
  32. data/spec/celluloid/actor_spec.rb +5 -0
  33. data/spec/celluloid/block_spec.rb +54 -0
  34. data/spec/celluloid/calls_spec.rb +42 -0
  35. data/spec/celluloid/condition_spec.rb +65 -0
  36. data/spec/celluloid/evented_mailbox_spec.rb +34 -0
  37. data/spec/celluloid/fsm_spec.rb +107 -0
  38. data/spec/celluloid/future_spec.rb +32 -0
  39. data/spec/celluloid/internal_pool_spec.rb +52 -0
  40. data/spec/celluloid/links_spec.rb +45 -0
  41. data/spec/celluloid/logging/ring_buffer_spec.rb +38 -0
  42. data/spec/celluloid/mailbox_spec.rb +5 -0
  43. data/spec/celluloid/notifications_spec.rb +120 -0
  44. data/spec/celluloid/pool_spec.rb +52 -0
  45. data/spec/celluloid/properties_spec.rb +42 -0
  46. data/spec/celluloid/registry_spec.rb +64 -0
  47. data/spec/celluloid/stack_dump_spec.rb +35 -0
  48. data/spec/celluloid/supervision_group_spec.rb +53 -0
  49. data/spec/celluloid/supervisor_spec.rb +92 -0
  50. data/spec/celluloid/tasks/task_fiber_spec.rb +5 -0
  51. data/spec/celluloid/tasks/task_thread_spec.rb +5 -0
  52. data/spec/celluloid/thread_handle_spec.rb +22 -0
  53. data/spec/celluloid/uuid_spec.rb +11 -0
  54. data/spec/spec_helper.rb +31 -0
  55. data/spec/support/actor_examples.rb +161 -10
  56. data/spec/support/example_actor_class.rb +8 -0
  57. data/spec/support/mailbox_examples.rb +15 -3
  58. data/spec/support/task_examples.rb +2 -2
  59. metadata +28 -3
  60. data/lib/celluloid/version.rb +0 -4
@@ -27,7 +27,7 @@ module Celluloid
27
27
  terminators = (@idle + @busy).each do |actor|
28
28
  begin
29
29
  actor.future(:terminate)
30
- rescue DeadActorError, MailboxError
30
+ rescue DeadActorError
31
31
  end
32
32
  end
33
33
 
@@ -0,0 +1,24 @@
1
+ module Celluloid
2
+ # Properties define inheritable attributes of classes, somewhat similar to
3
+ # Rails cattr_*/mattr_* or class_attribute
4
+ module Properties
5
+ def property(name, opts = {})
6
+ default = opts.fetch(:default, nil)
7
+ multi = opts.fetch(:multi, false)
8
+ ivar_name = "@#{name}".to_sym
9
+
10
+ ancestors.first.send(:define_singleton_method, name) do |value = nil, *extra|
11
+ if value
12
+ value = value ? [value, *extra] : [] if multi
13
+ instance_variable_set(ivar_name, value)
14
+ elsif instance_variables.include?(ivar_name)
15
+ instance_variable_get(ivar_name)
16
+ elsif superclass.respond_to? name
17
+ superclass.send(name)
18
+ else
19
+ default
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,9 @@
1
1
  module Celluloid
2
2
  # Base class of all Celluloid proxies
3
3
  class AbstractProxy < BasicObject
4
+ # Used for reflecting on proxy objects themselves
5
+ def __class__; AbstractProxy; end
6
+
4
7
  # Needed for storing proxies in data structures
5
8
  needed = [:object_id, :__id__, :hash] - instance_methods
6
9
  if needed.any?
@@ -4,6 +4,9 @@ module Celluloid
4
4
  class ActorProxy < SyncProxy
5
5
  attr_reader :thread
6
6
 
7
+ # Used for reflecting on proxy objects themselves
8
+ def __class__; ActorProxy; end
9
+
7
10
  def initialize(actor)
8
11
  @thread = actor.thread
9
12
 
@@ -13,14 +16,6 @@ module Celluloid
13
16
  @future_proxy = FutureProxy.new(@mailbox, @klass)
14
17
  end
15
18
 
16
- def class
17
- method_missing :__send__, :class
18
- end
19
-
20
- def send(meth, *args, &block)
21
- method_missing :send, meth, *args, &block
22
- end
23
-
24
19
  def _send_(meth, *args, &block)
25
20
  method_missing :__send__, meth, *args, &block
26
21
  end
@@ -31,26 +26,6 @@ module Celluloid
31
26
  "#<Celluloid::ActorProxy(#{@klass}) dead>"
32
27
  end
33
28
 
34
- def name
35
- method_missing :name
36
- end
37
-
38
- def is_a?(klass)
39
- method_missing :is_a?, klass
40
- end
41
-
42
- def kind_of?(klass)
43
- method_missing :kind_of?, klass
44
- end
45
-
46
- def respond_to?(meth, include_private = false)
47
- method_missing :respond_to?, meth, include_private
48
- end
49
-
50
- def methods(include_ancestors = true)
51
- method_missing :methods, include_ancestors
52
- end
53
-
54
29
  def method(name)
55
30
  Method.new(self, name)
56
31
  end
@@ -59,10 +34,6 @@ module Celluloid
59
34
  @mailbox.alive?
60
35
  end
61
36
 
62
- def to_s
63
- method_missing :to_s
64
- end
65
-
66
37
  alias_method :sync, :method_missing
67
38
 
68
39
  # Obtain an async proxy or explicitly invoke a named async method
@@ -3,6 +3,9 @@ module Celluloid
3
3
  class AsyncProxy < AbstractProxy
4
4
  attr_reader :mailbox
5
5
 
6
+ # Used for reflecting on proxy objects themselves
7
+ def __class__; AsyncProxy; end
8
+
6
9
  def initialize(mailbox, klass)
7
10
  @mailbox, @klass = mailbox, klass
8
11
  end
@@ -22,14 +25,7 @@ module Celluloid
22
25
  raise "Cannot use blocks with async yet"
23
26
  end
24
27
 
25
- begin
26
- @mailbox << AsyncCall.new(meth, args, block)
27
- rescue MailboxError
28
- # Silently swallow asynchronous calls to dead actors. There's no way
29
- # to reliably generate DeadActorErrors for async calls, so users of
30
- # async calls should find other ways to deal with actors dying
31
- # during an async call (i.e. linking/supervisors)
32
- end
28
+ @mailbox << AsyncCall.new(meth, args, block)
33
29
  end
34
30
  end
35
31
  end
@@ -3,6 +3,9 @@ module Celluloid
3
3
  class FutureProxy < AbstractProxy
4
4
  attr_reader :mailbox
5
5
 
6
+ # Used for reflecting on proxy objects themselves
7
+ def __class__; FutureProxy; end
8
+
6
9
  def initialize(mailbox, klass)
7
10
  @mailbox, @klass = mailbox, klass
8
11
  end
@@ -11,8 +14,11 @@ module Celluloid
11
14
  "#<Celluloid::FutureProxy(#{@klass})>"
12
15
  end
13
16
 
14
- # method_missing black magic to call bang predicate methods asynchronously
15
17
  def method_missing(meth, *args, &block)
18
+ unless @mailbox.alive?
19
+ raise DeadActorError, "attempted to call a dead actor"
20
+ end
21
+
16
22
  if block_given?
17
23
  # FIXME: nicer exception
18
24
  raise "Cannot use blocks with futures yet"
@@ -21,11 +27,7 @@ module Celluloid
21
27
  future = Future.new
22
28
  call = SyncCall.new(future, meth, args, block)
23
29
 
24
- begin
25
- @mailbox << call
26
- rescue MailboxError
27
- raise DeadActorError, "attempted to call a dead actor"
28
- end
30
+ @mailbox << call
29
31
 
30
32
  future
31
33
  end
@@ -3,6 +3,9 @@ module Celluloid
3
3
  class SyncProxy < AbstractProxy
4
4
  attr_reader :mailbox
5
5
 
6
+ # Used for reflecting on proxy objects themselves
7
+ def __class__; SyncProxy; end
8
+
6
9
  def initialize(mailbox, klass)
7
10
  @mailbox, @klass = mailbox, klass
8
11
  end
@@ -11,20 +14,22 @@ module Celluloid
11
14
  "#<Celluloid::SyncProxy(#{@klass})>"
12
15
  end
13
16
 
17
+ def respond_to?(meth, include_private = false)
18
+ __class__.instance_methods.include?(meth) || super
19
+ end
20
+
14
21
  def method_missing(meth, *args, &block)
22
+ unless @mailbox.alive?
23
+ raise DeadActorError, "attempted to call a dead actor"
24
+ end
25
+
15
26
  if @mailbox == ::Thread.current[:celluloid_mailbox]
16
27
  args.unshift meth
17
28
  meth = :__send__
18
29
  end
19
30
 
20
31
  call = SyncCall.new(::Celluloid.mailbox, meth, args, block)
21
-
22
- begin
23
- @mailbox << call
24
- rescue MailboxError
25
- raise DeadActorError, "attempted to call a dead actor"
26
- end
27
-
32
+ @mailbox << call
28
33
  call.value
29
34
  end
30
35
  end
@@ -5,4 +5,6 @@ require File.expand_path('../../../spec/support/mailbox_examples', __FILE__)
5
5
  module Celluloid
6
6
  # Timer accuracy enforced by the tests (50ms)
7
7
  TIMER_QUANTUM = 0.05
8
- end
8
+ end
9
+
10
+ $CELLULOID_DEBUG = true
@@ -1,51 +1,23 @@
1
1
  module Celluloid
2
2
  # Event signaling between methods of the same object
3
3
  class Signals
4
- attr_reader :waiting
5
-
6
4
  def initialize
7
- @waiting = {}
5
+ @conditions = {}
8
6
  end
9
7
 
10
8
  # Wait for the given signal and return the associated value
11
- def wait(signal)
9
+ def wait(name)
12
10
  raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
13
11
 
14
- tasks = @waiting[signal]
15
- case tasks
16
- when Array
17
- tasks << Task.current
18
- when NilClass
19
- @waiting[signal] = Task.current
20
- else
21
- @waiting[signal] = [tasks, Task.current]
22
- end
23
-
24
- Task.suspend :sigwait
12
+ @conditions[name] ||= Condition.new
13
+ @conditions[name].wait
25
14
  end
26
15
 
27
16
  # Send a signal to all method calls waiting for the given name
28
- # Returns true if any calls were signaled, or false otherwise
29
- def send(name, value = nil)
30
- tasks = @waiting.delete name
31
-
32
- case tasks
33
- when Array
34
- tasks.each { |task| run_task task, value }
35
- true if tasks.size > 0
36
- when NilClass
37
- false
38
- else
39
- run_task tasks, value
40
- true
17
+ def broadcast(name, value = nil)
18
+ if condition = @conditions.delete(name)
19
+ condition.broadcast(value)
41
20
  end
42
21
  end
43
-
44
- # Run the given task, reporting errors that occur
45
- def run_task(task, value)
46
- task.resume(value)
47
- rescue => ex
48
- Logger.crash("signaling error", ex)
49
- end
50
22
  end
51
23
  end
@@ -1,16 +1,60 @@
1
1
  module Celluloid
2
2
  class StackDump
3
+ module DisplayBacktrace
4
+ def display_backtrace(backtrace, output, indent = nil)
5
+ backtrace ||= ["EMPTY BACKTRACE"]
6
+ backtrace.each do |line|
7
+ output << indent if indent
8
+ output << "\t" << line << "\n"
9
+ end
10
+ output << "\n\n"
11
+ end
12
+ end
3
13
 
4
- class TaskState < Struct.new(:task_class, :status, :backtrace)
14
+ class TaskState < Struct.new(:task_class, :type, :meta, :status, :backtrace)
5
15
  end
6
16
 
7
17
  class ActorState
18
+ include DisplayBacktrace
19
+
8
20
  attr_accessor :subject_id, :subject_class, :name
9
21
  attr_accessor :status, :tasks
10
22
  attr_accessor :backtrace
23
+
24
+ def dump
25
+ string = ""
26
+ string << "Celluloid::Actor 0x#{subject_id.to_s(16)}: #{subject_class}"
27
+ string << " [#{name}]" if name
28
+ string << "\n"
29
+
30
+ if status == :idle
31
+ string << "State: Idle (waiting for messages)\n"
32
+ display_backtrace backtrace, string
33
+ else
34
+ string << "State: Running (executing tasks)\n"
35
+ display_backtrace backtrace, string
36
+ string << "\tTasks:\n"
37
+
38
+ tasks.each_with_index do |task, i|
39
+ string << "\t #{i+1}) #{task.task_class}[#{task.type}]: #{task.status}\n"
40
+ string << "\t #{task.meta.inspect}\n"
41
+ display_backtrace task.backtrace, string, "\t"
42
+ end
43
+ end
44
+
45
+ string
46
+ end
11
47
  end
12
48
 
13
49
  class ThreadState < Struct.new(:thread_id, :backtrace)
50
+ include DisplayBacktrace
51
+
52
+ def dump
53
+ string = ""
54
+ string << "Thread 0x#{thread_id.to_s(16)}:\n"
55
+ display_backtrace backtrace, string
56
+ string
57
+ end
14
58
  end
15
59
 
16
60
  attr_accessor :actors, :threads
@@ -23,9 +67,8 @@ module Celluloid
23
67
  end
24
68
 
25
69
  def snapshot
26
- Thread.list.each do |thread|
27
- if thread.celluloid?
28
- next unless thread.role == :actor
70
+ Celluloid.internal_pool.each do |thread|
71
+ if thread.role == :actor
29
72
  @actors << snapshot_actor(thread.actor) if thread.actor
30
73
  else
31
74
  @threads << snapshot_thread(thread)
@@ -43,7 +86,7 @@ module Celluloid
43
86
  state.status = :idle
44
87
  else
45
88
  state.status = :running
46
- state.tasks = tasks.collect { |t| TaskState.new(t.class, t.status, t.backtrace) }
89
+ state.tasks = tasks.to_a.map { |t| TaskState.new(t.class, t.type, t.meta, t.status, t.backtrace) }
47
90
  end
48
91
 
49
92
  state.backtrace = actor.thread.backtrace if actor.thread
@@ -56,41 +99,11 @@ module Celluloid
56
99
 
57
100
  def dump(output = STDERR)
58
101
  @actors.each do |actor|
59
- string = ""
60
- string << "Celluloid::Actor 0x#{actor.subject_id.to_s(16)}: #{actor.subject_class}"
61
- string << " [#{actor.name}]" if actor.name
62
- string << "\n"
63
-
64
- if actor.status == :idle
65
- string << "State: Idle (waiting for messages)\n"
66
- display_backtrace actor.backtrace, string
67
- else
68
- string << "State: Running (executing tasks)\n"
69
- display_backtrace actor.backtrace, string
70
- string << "Tasks:\n"
71
-
72
- actor.tasks.each_with_index do |task, i|
73
- string << " #{i+1}) #{task.task_class}: #{task.status}\n"
74
- display_backtrace task.backtrace, string
75
- end
76
- end
77
-
78
- output.print string
102
+ output.print actor.dump
79
103
  end
80
104
 
81
105
  @threads.each do |thread|
82
- string = ""
83
- string << "Thread 0x#{thread.thread_id.to_s(16)}:\n"
84
- display_backtrace thread.backtrace, string
85
- output.print string
86
- end
87
- end
88
-
89
- def display_backtrace(backtrace, output)
90
- if backtrace
91
- output << "\t" << backtrace.join("\n\t") << "\n\n"
92
- else
93
- output << "EMPTY BACKTRACE\n\n"
106
+ output.print thread.dump
94
107
  end
95
108
  end
96
109
  end
@@ -11,8 +11,8 @@ module Celluloid
11
11
  end
12
12
 
13
13
  # Start this application (and watch it with a supervisor)
14
- def run!
15
- group = new do |_group|
14
+ def run!(registry = nil)
15
+ group = new(registry) do |_group|
16
16
  blocks.each do |block|
17
17
  block.call(_group)
18
18
  end
@@ -21,9 +21,9 @@ module Celluloid
21
21
  end
22
22
 
23
23
  # Run the application in the foreground with a simple watchdog
24
- def run
24
+ def run(registry = nil)
25
25
  loop do
26
- supervisor = run!
26
+ supervisor = run!(registry)
27
27
 
28
28
  # Take five, toplevel supervisor
29
29
  sleep 5 while supervisor.alive?
@@ -149,7 +149,7 @@ module Celluloid
149
149
 
150
150
  def terminate
151
151
  @actor.terminate if @actor
152
- rescue DeadActorError, MailboxError
152
+ rescue DeadActorError
153
153
  end
154
154
  end
155
155
  end
@@ -0,0 +1,49 @@
1
+ require 'set'
2
+ require 'forwardable'
3
+
4
+ module Celluloid
5
+ if defined? JRUBY_VERSION
6
+ require 'jruby/synchronized'
7
+
8
+ class TaskSet
9
+ extend Forwardable
10
+ include JRuby::Synchronized
11
+
12
+ def_delegators :@tasks, :<<, :delete, :first, :empty?, :to_a
13
+
14
+ def initialize
15
+ @tasks = Set.new
16
+ end
17
+ end
18
+ elsif defined? Rubinius
19
+ class TaskSet
20
+ def initialize
21
+ @tasks = Set.new
22
+ end
23
+
24
+ def <<(task)
25
+ Rubinius.synchronize(self) { @tasks << task }
26
+ end
27
+
28
+ def delete(task)
29
+ Rubinius.synchronize(self) { @tasks.delete task }
30
+ end
31
+
32
+ def first
33
+ Rubinius.synchronize(self) { @tasks.first }
34
+ end
35
+
36
+ def empty?
37
+ Rubinius.synchronize(self) { @tasks.empty? }
38
+ end
39
+
40
+ def to_a
41
+ Rubinius.synchronize(self) { @tasks.to_a }
42
+ end
43
+ end
44
+ else
45
+ # Assume we're on MRI, where we have the GIL. But what about IronRuby?
46
+ # Or MacRuby. Do people care? This will break Celluloid::StackDumps
47
+ TaskSet = Set
48
+ end
49
+ end