celluloid 0.14.1 → 0.15.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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