celluloid 0.15.2 → 0.16.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +20 -0
  3. data/README.md +29 -2
  4. data/lib/celluloid.rb +68 -73
  5. data/lib/celluloid/actor.rb +69 -123
  6. data/lib/celluloid/actor_system.rb +107 -0
  7. data/lib/celluloid/calls.rb +16 -16
  8. data/lib/celluloid/cell.rb +89 -0
  9. data/lib/celluloid/condition.rb +25 -8
  10. data/lib/celluloid/cpu_counter.rb +2 -0
  11. data/lib/celluloid/evented_mailbox.rb +2 -1
  12. data/lib/celluloid/exceptions.rb +23 -0
  13. data/lib/celluloid/future.rb +1 -1
  14. data/lib/celluloid/handlers.rb +41 -0
  15. data/lib/celluloid/internal_pool.rb +0 -3
  16. data/lib/celluloid/logger.rb +30 -0
  17. data/lib/celluloid/logging/incident_logger.rb +1 -1
  18. data/lib/celluloid/mailbox.rb +19 -18
  19. data/lib/celluloid/method.rb +8 -0
  20. data/lib/celluloid/pool_manager.rb +1 -1
  21. data/lib/celluloid/probe.rb +73 -0
  22. data/lib/celluloid/properties.rb +2 -2
  23. data/lib/celluloid/proxies/actor_proxy.rb +9 -41
  24. data/lib/celluloid/proxies/cell_proxy.rb +68 -0
  25. data/lib/celluloid/proxies/sync_proxy.rb +1 -1
  26. data/lib/celluloid/receivers.rb +1 -0
  27. data/lib/celluloid/registry.rb +1 -8
  28. data/lib/celluloid/stack_dump.rb +34 -11
  29. data/lib/celluloid/supervision_group.rb +26 -14
  30. data/lib/celluloid/tasks.rb +6 -9
  31. data/lib/celluloid/tasks/task_fiber.rb +6 -0
  32. data/lib/celluloid/tasks/task_thread.rb +2 -1
  33. data/lib/celluloid/thread_handle.rb +2 -2
  34. data/spec/celluloid/actor_spec.rb +1 -1
  35. data/spec/celluloid/actor_system_spec.rb +69 -0
  36. data/spec/celluloid/block_spec.rb +1 -1
  37. data/spec/celluloid/calls_spec.rb +1 -1
  38. data/spec/celluloid/condition_spec.rb +14 -3
  39. data/spec/celluloid/cpu_counter_spec.rb +9 -0
  40. data/spec/celluloid/fsm_spec.rb +1 -1
  41. data/spec/celluloid/future_spec.rb +1 -1
  42. data/spec/celluloid/notifications_spec.rb +1 -1
  43. data/spec/celluloid/pool_spec.rb +1 -1
  44. data/spec/celluloid/probe_spec.rb +121 -0
  45. data/spec/celluloid/registry_spec.rb +6 -6
  46. data/spec/celluloid/stack_dump_spec.rb +37 -8
  47. data/spec/celluloid/supervision_group_spec.rb +7 -1
  48. data/spec/celluloid/supervisor_spec.rb +12 -1
  49. data/spec/celluloid/tasks/task_fiber_spec.rb +1 -1
  50. data/spec/celluloid/tasks/task_thread_spec.rb +1 -1
  51. data/spec/celluloid/thread_handle_spec.rb +7 -3
  52. data/spec/spec_helper.rb +20 -7
  53. data/spec/support/actor_examples.rb +33 -15
  54. data/spec/support/mailbox_examples.rb +9 -3
  55. data/spec/support/task_examples.rb +2 -0
  56. metadata +32 -22
@@ -1,8 +1,38 @@
1
1
  module Celluloid
2
2
  module Logger
3
+ class WithBacktrace
4
+ def initialize(backtrace)
5
+ @backtrace = backtrace
6
+ end
7
+
8
+ def debug(string)
9
+ Celluloid.logger.debug(decorate(string))
10
+ end
11
+
12
+ def info(string)
13
+ Celluloid.logger.info(decorate(string))
14
+ end
15
+
16
+ def warn(string)
17
+ Celluloid.logger.warn(decorate(string))
18
+ end
19
+
20
+ def error(string)
21
+ Celluloid.logger.error(decorate(string))
22
+ end
23
+
24
+ def decorate(string)
25
+ [string, @backtrace].join("\n\t")
26
+ end
27
+ end
28
+
3
29
  @exception_handlers = []
4
30
  module_function
5
31
 
32
+ def with_backtrace(backtrace)
33
+ yield WithBacktrace.new(backtrace) if Celluloid.logger
34
+ end
35
+
6
36
  # Send a debug message
7
37
  def debug(string)
8
38
  Celluloid.logger.debug(string) if Celluloid.logger
@@ -111,7 +111,7 @@ module Celluloid
111
111
  end
112
112
  messages.sort
113
113
  end
114
-
114
+
115
115
  def clear
116
116
  @buffer_mutex.synchronize do
117
117
  @buffers.each { |buffer| buffer.clear }
@@ -58,10 +58,11 @@ module Celluloid
58
58
 
59
59
  unless message
60
60
  if timeout
61
+ # TODO: use hitimes/timers instead of Time.now
61
62
  now = Time.now
62
63
  wait_until ||= now + timeout
63
64
  wait_interval = wait_until - now
64
- return if wait_interval <= 0
65
+ raise(TimeoutError, "mailbox timeout exceeded", nil) if wait_interval <= 0
65
66
  else
66
67
  wait_interval = nil
67
68
  end
@@ -76,23 +77,6 @@ module Celluloid
76
77
  end
77
78
  end
78
79
 
79
- # Retrieve the next message in the mailbox
80
- def next_message
81
- message = nil
82
-
83
- if block_given?
84
- index = @messages.index do |msg|
85
- yield(msg) || msg.is_a?(SystemEvent)
86
- end
87
-
88
- message = @messages.slice!(index, 1).first if index
89
- else
90
- message = @messages.shift
91
- end
92
-
93
- message
94
- end
95
-
96
80
  # Shut down this mailbox and clean up its contents
97
81
  def shutdown
98
82
  raise MailboxDead, "mailbox already shutdown" if @dead
@@ -141,6 +125,23 @@ module Celluloid
141
125
 
142
126
  private
143
127
 
128
+ # Retrieve the next message in the mailbox
129
+ def next_message
130
+ message = nil
131
+
132
+ if block_given?
133
+ index = @messages.index do |msg|
134
+ yield(msg) || msg.is_a?(SystemEvent)
135
+ end
136
+
137
+ message = @messages.slice!(index, 1).first if index
138
+ else
139
+ message = @messages.shift
140
+ end
141
+
142
+ message
143
+ end
144
+
144
145
  def dead_letter(message)
145
146
  Logger.debug "Discarded message (mailbox is dead): #{message}" if $CELLULOID_DEBUG
146
147
  end
@@ -13,6 +13,14 @@ module Celluloid
13
13
  @proxy.method_missing(:method, @name).arity
14
14
  end
15
15
 
16
+ def name
17
+ @proxy.method_missing(:method, @name).name
18
+ end
19
+
20
+ def parameters
21
+ @proxy.method_missing(:method, @name).parameters
22
+ end
23
+
16
24
  def call(*args, &block)
17
25
  @proxy.__send__(@name, *args, &block)
18
26
  end
@@ -10,7 +10,7 @@ module Celluloid
10
10
  finalizer :__shutdown__
11
11
 
12
12
  def initialize(worker_class, options = {})
13
- @size = options[:size] || [Celluloid.cores, 2].max
13
+ @size = options[:size] || [Celluloid.cores || 2, 2].max
14
14
  raise ArgumentError, "minimum pool size is 2" if @size < 2
15
15
 
16
16
  @worker_class = worker_class
@@ -0,0 +1,73 @@
1
+ require 'celluloid'
2
+
3
+ $CELLULOID_MONITORING = true
4
+
5
+ module Celluloid
6
+ class Probe
7
+ include Celluloid
8
+ include Celluloid::Notifications
9
+
10
+ NOTIFICATIONS_TOPIC_BASE = 'celluloid.events.%s'
11
+ INITIAL_EVENTS = Queue.new
12
+
13
+ class << self
14
+ def run
15
+ # spawn the actor if not found
16
+ supervise_as(:probe_actor) unless Actor[:probe_actor] && Actor[:probe_actor].alive?
17
+ end
18
+
19
+ def actor_created(actor)
20
+ trigger_event(:actor_created, actor)
21
+ end
22
+
23
+ def actor_named(actor)
24
+ trigger_event(:actor_named, actor)
25
+ end
26
+
27
+ def actor_died(actor)
28
+ trigger_event(:actor_died, actor)
29
+ end
30
+
31
+ def actors_linked(a, b)
32
+ a = find_actor(a)
33
+ b = find_actor(b)
34
+ trigger_event(:actors_linked, a, b)
35
+ end
36
+
37
+ private
38
+
39
+ def trigger_event(name, *args)
40
+ return unless $CELLULOID_MONITORING
41
+ probe_actor = Actor[:probe_actor]
42
+ if probe_actor
43
+ probe_actor.async.dispatch_event(name, args)
44
+ else
45
+ INITIAL_EVENTS << [name, args]
46
+ end
47
+ end
48
+
49
+ def find_actor(obj)
50
+ if obj.__send__(:class) == Actor
51
+ obj
52
+ elsif owner = obj.instance_variable_get(OWNER_IVAR)
53
+ owner
54
+ end
55
+ end
56
+ end
57
+
58
+ def initialize
59
+ async.first_run
60
+ end
61
+
62
+ def first_run
63
+ until INITIAL_EVENTS.size == 0
64
+ event = INITIAL_EVENTS.pop
65
+ dispatch_event(*event)
66
+ end
67
+ end
68
+
69
+ def dispatch_event(cmd, args)
70
+ publish(NOTIFICATIONS_TOPIC_BASE % cmd, args)
71
+ end
72
+ end
73
+ end
@@ -9,7 +9,7 @@ module Celluloid
9
9
 
10
10
  ancestors.first.send(:define_singleton_method, name) do |value = nil, *extra|
11
11
  if value
12
- value = value ? [value, *extra] : [] if multi
12
+ value = value ? [value, *send(name), *extra].uniq : [] if multi
13
13
  instance_variable_set(ivar_name, value)
14
14
  elsif instance_variables.include?(ivar_name)
15
15
  instance_variable_get(ivar_name)
@@ -21,4 +21,4 @@ module Celluloid
21
21
  end
22
22
  end
23
23
  end
24
- end
24
+ end
@@ -1,59 +1,27 @@
1
1
  module Celluloid
2
- # A proxy object returned from Celluloid::Actor.new/new_link which converts
3
- # the normal Ruby method protocol into an inter-actor message protocol
4
- class ActorProxy < SyncProxy
5
- attr_reader :thread
2
+ # A proxy which controls the Actor lifecycle
3
+ class ActorProxy < AbstractProxy
4
+ attr_reader :thread, :mailbox
6
5
 
7
6
  # Used for reflecting on proxy objects themselves
8
7
  def __class__; ActorProxy; end
9
8
 
10
- def initialize(actor)
11
- @thread = actor.thread
12
-
13
- super(actor.mailbox, actor.subject.class.to_s)
14
- @sync_proxy = SyncProxy.new(@mailbox, @klass)
15
- @async_proxy = AsyncProxy.new(@mailbox, @klass)
16
- @future_proxy = FutureProxy.new(@mailbox, @klass)
17
- end
18
-
19
- def _send_(meth, *args, &block)
20
- method_missing :__send__, meth, *args, &block
9
+ def initialize(thread, mailbox)
10
+ @thread = thread
11
+ @mailbox = mailbox
21
12
  end
22
13
 
23
14
  def inspect
24
- method_missing :inspect
15
+ # TODO: use a system event to fetch actor state: tasks?
16
+ "#<Celluloid::ActorProxy(#{@mailbox.address}) alive>"
25
17
  rescue DeadActorError
26
- "#<Celluloid::ActorProxy(#{@klass}) dead>"
27
- end
28
-
29
- def method(name)
30
- Method.new(self, name)
18
+ "#<Celluloid::ActorProxy(#{@mailbox.address}) dead>"
31
19
  end
32
20
 
33
21
  def alive?
34
22
  @mailbox.alive?
35
23
  end
36
24
 
37
- alias_method :sync, :method_missing
38
-
39
- # Obtain an async proxy or explicitly invoke a named async method
40
- def async(method_name = nil, *args, &block)
41
- if method_name
42
- @async_proxy.method_missing method_name, *args, &block
43
- else
44
- @async_proxy
45
- end
46
- end
47
-
48
- # Obtain a future proxy or explicitly invoke a named future method
49
- def future(method_name = nil, *args, &block)
50
- if method_name
51
- @future_proxy.method_missing method_name, *args, &block
52
- else
53
- @future_proxy
54
- end
55
- end
56
-
57
25
  # Terminate the associated actor
58
26
  def terminate
59
27
  terminate!
@@ -0,0 +1,68 @@
1
+ module Celluloid
2
+ # A proxy object returned from Celluloid::Actor.new/new_link which converts
3
+ # the normal Ruby method protocol into an inter-actor message protocol
4
+ class CellProxy < SyncProxy
5
+ # Used for reflecting on proxy objects themselves
6
+ def __class__; CellProxy; end
7
+
8
+ def initialize(actor_proxy, mailbox, klass)
9
+ super(mailbox, klass)
10
+ @actor_proxy = actor_proxy
11
+ @sync_proxy = SyncProxy.new(mailbox, klass)
12
+ @async_proxy = AsyncProxy.new(mailbox, klass)
13
+ @future_proxy = FutureProxy.new(mailbox, klass)
14
+ end
15
+
16
+ def _send_(meth, *args, &block)
17
+ method_missing :__send__, meth, *args, &block
18
+ end
19
+
20
+ def inspect
21
+ method_missing :inspect
22
+ rescue DeadActorError
23
+ "#<Celluloid::CellProxy(#{@klass}) dead>"
24
+ end
25
+
26
+ def method(name)
27
+ Method.new(self, name)
28
+ end
29
+
30
+ alias_method :sync, :method_missing
31
+
32
+ # Obtain an async proxy or explicitly invoke a named async method
33
+ def async(method_name = nil, *args, &block)
34
+ if method_name
35
+ @async_proxy.method_missing method_name, *args, &block
36
+ else
37
+ @async_proxy
38
+ end
39
+ end
40
+
41
+ # Obtain a future proxy or explicitly invoke a named future method
42
+ def future(method_name = nil, *args, &block)
43
+ if method_name
44
+ @future_proxy.method_missing method_name, *args, &block
45
+ else
46
+ @future_proxy
47
+ end
48
+ end
49
+
50
+ def alive?
51
+ @actor_proxy.alive?
52
+ end
53
+
54
+ def thread
55
+ @actor_proxy.thread
56
+ end
57
+
58
+ # Terminate the associated actor
59
+ def terminate
60
+ @actor_proxy.terminate
61
+ end
62
+
63
+ # Terminate the associated actor asynchronously
64
+ def terminate!
65
+ @actor_proxy.terminate!
66
+ end
67
+ end
68
+ end
@@ -15,7 +15,7 @@ module Celluloid
15
15
  end
16
16
 
17
17
  def respond_to?(meth, include_private = false)
18
- __class__.instance_methods.include?(meth) || super
18
+ __class__.instance_methods.include?(meth) || method_missing(:respond_to?, meth, include_private)
19
19
  end
20
20
 
21
21
  def method_missing(meth, *args, &block)
@@ -46,6 +46,7 @@ module Celluloid
46
46
  @receivers.delete receiver
47
47
  @timers.cancel receiver.timer if receiver.timer
48
48
  receiver.resume message
49
+ message
49
50
  end
50
51
  end
51
52
 
@@ -3,10 +3,6 @@ require 'thread'
3
3
  module Celluloid
4
4
  # The Registry allows us to refer to specific actors by human-meaningful names
5
5
  class Registry
6
- class << self
7
- attr_reader :root
8
- end
9
-
10
6
  def initialize
11
7
  @registry = {}
12
8
  @registry_lock = Mutex.new
@@ -15,7 +11,7 @@ module Celluloid
15
11
  # Register an Actor
16
12
  def []=(name, actor)
17
13
  actor_singleton = class << actor; self; end
18
- unless actor_singleton.ancestors.include? ActorProxy
14
+ unless actor_singleton.ancestors.include? AbstractProxy
19
15
  raise TypeError, "not an actor"
20
16
  end
21
17
 
@@ -57,8 +53,5 @@ module Celluloid
57
53
  end
58
54
  hash
59
55
  end
60
-
61
- # Create the default registry
62
- @root = new
63
56
  end
64
57
  end
@@ -16,17 +16,21 @@ module Celluloid
16
16
 
17
17
  class ActorState
18
18
  include DisplayBacktrace
19
-
20
- attr_accessor :subject_id, :subject_class, :name
19
+ attr_accessor :name, :id, :cell
21
20
  attr_accessor :status, :tasks
22
21
  attr_accessor :backtrace
23
22
 
24
23
  def dump
25
24
  string = ""
26
- string << "Celluloid::Actor 0x#{subject_id.to_s(16)}: #{subject_class}"
25
+ string << "Celluloid::Actor 0x#{id.to_s(16)}"
27
26
  string << " [#{name}]" if name
28
27
  string << "\n"
29
28
 
29
+ if cell
30
+ string << cell.dump
31
+ string << "\n"
32
+ end
33
+
30
34
  if status == :idle
31
35
  string << "State: Idle (waiting for messages)\n"
32
36
  display_backtrace backtrace, string
@@ -46,12 +50,18 @@ module Celluloid
46
50
  end
47
51
  end
48
52
 
49
- class ThreadState < Struct.new(:thread_id, :backtrace)
53
+ class CellState < Struct.new(:subject_id, :subject_class)
54
+ def dump
55
+ "Celluloid::Cell 0x#{subject_id.to_s(16)}: #{subject_class}"
56
+ end
57
+ end
58
+
59
+ class ThreadState < Struct.new(:thread_id, :backtrace, :role)
50
60
  include DisplayBacktrace
51
61
 
52
62
  def dump
53
63
  string = ""
54
- string << "Thread 0x#{thread_id.to_s(16)}:\n"
64
+ string << "Thread 0x#{thread_id.to_s(16)} (#{role}):\n"
55
65
  display_backtrace backtrace, string
56
66
  string
57
67
  end
@@ -59,7 +69,9 @@ module Celluloid
59
69
 
60
70
  attr_accessor :actors, :threads
61
71
 
62
- def initialize
72
+ def initialize(internal_pool)
73
+ @internal_pool = internal_pool
74
+
63
75
  @actors = []
64
76
  @threads = []
65
77
 
@@ -67,7 +79,7 @@ module Celluloid
67
79
  end
68
80
 
69
81
  def snapshot
70
- Celluloid.internal_pool.each do |thread|
82
+ @internal_pool.each do |thread|
71
83
  if thread.role == :actor
72
84
  @actors << snapshot_actor(thread.actor) if thread.actor
73
85
  else
@@ -78,8 +90,12 @@ module Celluloid
78
90
 
79
91
  def snapshot_actor(actor)
80
92
  state = ActorState.new
81
- state.subject_id = actor.subject.object_id
82
- state.subject_class = actor.subject.class
93
+ state.id = actor.object_id
94
+
95
+ # TODO: delegate to the behavior
96
+ if actor.behavior.is_a?(Cell)
97
+ state.cell = snapshot_cell(actor.behavior)
98
+ end
83
99
 
84
100
  tasks = actor.tasks
85
101
  if tasks.empty?
@@ -93,11 +109,18 @@ module Celluloid
93
109
  state
94
110
  end
95
111
 
112
+ def snapshot_cell(behavior)
113
+ state = CellState.new
114
+ state.subject_id = behavior.subject.object_id
115
+ state.subject_class = behavior.subject.class
116
+ state
117
+ end
118
+
96
119
  def snapshot_thread(thread)
97
- ThreadState.new(thread.object_id, thread.backtrace)
120
+ ThreadState.new(thread.object_id, thread.backtrace, thread.role)
98
121
  end
99
122
 
100
- def dump(output = STDERR)
123
+ def print(output = STDERR)
101
124
  @actors.each do |actor|
102
125
  output.print actor.dump
103
126
  end