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
@@ -5,6 +5,7 @@ module Celluloid
5
5
  trap_exit :restart_actor
6
6
 
7
7
  class << self
8
+
8
9
  # Actors or sub-applications to be supervised
9
10
  def blocks
10
11
  @blocks ||= []
@@ -55,10 +56,12 @@ module Celluloid
55
56
  end
56
57
  end
57
58
 
59
+ finalizer :finalize
60
+
58
61
  # Start the group
59
62
  def initialize(registry = nil)
60
63
  @members = []
61
- @registry = registry || Registry.root
64
+ @registry = registry || Celluloid.actor_system.registry
62
65
 
63
66
  yield current_actor if block_given?
64
67
  end
@@ -81,18 +84,15 @@ module Celluloid
81
84
  def add(klass, options)
82
85
  member = Member.new(@registry, klass, options)
83
86
  @members << member
84
- member
87
+ member.actor
85
88
  end
86
89
 
87
90
  def actors
88
91
  @members.map(&:actor)
89
92
  end
90
93
 
91
- finalizer :finalize
92
-
93
- # Terminate the group
94
- def finalize
95
- @members.reverse_each(&:terminate)
94
+ def [](actor_name)
95
+ @registry[actor_name]
96
96
  end
97
97
 
98
98
  # Restart a crashed actor
@@ -102,7 +102,12 @@ module Celluloid
102
102
  end
103
103
  raise "a group member went missing. This shouldn't be!" unless member
104
104
 
105
- member.restart(reason)
105
+ if reason
106
+ member.restart
107
+ else
108
+ member.cleanup
109
+ @members.delete(member)
110
+ end
106
111
  end
107
112
 
108
113
  # A member of the group
@@ -137,21 +142,28 @@ module Celluloid
137
142
  @registry[@name] = @actor if @name
138
143
  end
139
144
 
140
- def restart(reason)
145
+ def restart
141
146
  @actor = nil
142
- @registry.delete(@name) if @name
143
-
144
- # Ignore supervisors that shut down cleanly
145
- return unless reason
147
+ cleanup
146
148
 
147
149
  start
148
150
  end
149
151
 
150
152
  def terminate
151
- @registry.delete(@name) if @name
153
+ cleanup
152
154
  @actor.terminate if @actor
153
155
  rescue DeadActorError
154
156
  end
157
+
158
+ def cleanup
159
+ @registry.delete(@name) if @name
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ def finalize
166
+ @members.reverse_each(&:terminate) if @members
155
167
  end
156
168
  end
157
169
  end
@@ -74,14 +74,9 @@ module Celluloid
74
74
  @status = status
75
75
 
76
76
  if $CELLULOID_DEBUG && @dangerous_suspend
77
- warning = "Dangerously suspending task: "
78
- warning << [
79
- "type=#{@type.inspect}",
80
- "meta=#{@meta.inspect}",
81
- "status=#{@status.inspect}"
82
- ].join(", ")
83
-
84
- Logger.warn [warning, *caller[2..8]].join("\n\t")
77
+ Logger.with_backtrace(caller[2...8]) do |logger|
78
+ logger.warn "Dangerously suspending task: type=#{@type.inspect}, meta=#{@meta.inspect}, status=#{@status.inspect}"
79
+ end
85
80
  end
86
81
 
87
82
  value = signal
@@ -118,7 +113,9 @@ module Celluloid
118
113
  raise "Cannot terminate an exclusive task" if exclusive?
119
114
 
120
115
  if running?
121
- Celluloid.logger.warn "Terminating task: type=#{@type.inspect}, meta=#{@meta.inspect}, status=#{@status.inspect}"
116
+ Logger.with_backtrace(backtrace) do |logger|
117
+ logger.warn "Terminating task: type=#{@type.inspect}, meta=#{@meta.inspect}, status=#{@status.inspect}"
118
+ end
122
119
  exception = Task::TerminatedError.new("task was terminated")
123
120
  exception.set_backtrace(caller)
124
121
  resume exception
@@ -6,10 +6,12 @@ module Celluloid
6
6
 
7
7
  def create
8
8
  queue = Thread.current[:celluloid_queue]
9
+ actor_system = Thread.current[:celluloid_actor_system]
9
10
  @fiber = Fiber.new do
10
11
  # FIXME: cannot use the writer as specs run inside normal Threads
11
12
  Thread.current[:celluloid_role] = :actor
12
13
  Thread.current[:celluloid_queue] = queue
14
+ Thread.current[:celluloid_actor_system] = actor_system
13
15
  yield
14
16
  end
15
17
  end
@@ -33,5 +35,9 @@ module Celluloid
33
35
  rescue FiberError
34
36
  # If we're getting this the task should already be dead
35
37
  end
38
+
39
+ def backtrace
40
+ "#{self.class} backtrace unavailable. Please try `Celluloid.task_class = Celluloid::TaskThread` if you need backtraces here."
41
+ end
36
42
  end
37
43
  end
@@ -12,7 +12,8 @@ module Celluloid
12
12
  end
13
13
 
14
14
  def create
15
- @thread = Celluloid::ThreadHandle.new(:task) do
15
+ # TODO: move this to ActorSystem#get_thread (ThreadHandle inside InternalPool)
16
+ @thread = ThreadHandle.new(Thread.current[:celluloid_actor_system], :task) do
16
17
  begin
17
18
  ex = @resume_queue.pop
18
19
  raise ex if ex.is_a?(Task::TerminatedError)
@@ -3,11 +3,11 @@ 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(role = nil)
6
+ def initialize(actor_system, role = nil)
7
7
  @mutex = Mutex.new
8
8
  @join = ConditionVariable.new
9
9
 
10
- @thread = Celluloid.internal_pool.get do
10
+ @thread = actor_system.get_thread do
11
11
  Thread.current.role = role
12
12
  begin
13
13
  yield
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid do
3
+ describe Celluloid, actor_system: :global do
4
4
  it_behaves_like "a Celluloid Actor", Celluloid
5
5
  end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::ActorSystem do
4
+ class TestActor
5
+ include Celluloid
6
+ end
7
+
8
+ it "supports non-global ActorSystem" do
9
+ subject.within do
10
+ Celluloid.actor_system.should == subject
11
+ end
12
+ end
13
+
14
+ it "starts default actors" do
15
+ subject.start
16
+
17
+ subject.registered.should == [:notifications_fanout, :default_incident_reporter]
18
+ end
19
+
20
+ it "support getting threads" do
21
+ queue = Queue.new
22
+ thread = subject.get_thread do
23
+ Celluloid.actor_system.should == subject
24
+ queue << nil
25
+ end
26
+ queue.pop
27
+ end
28
+
29
+ it "allows a stack dump" do
30
+ subject.stack_dump.should be_a(Celluloid::StackDump)
31
+ end
32
+
33
+ it "returns named actors" do
34
+ subject.registered.should be_empty
35
+
36
+ subject.within do
37
+ TestActor.supervise_as :test
38
+ end
39
+
40
+ subject.registered.should == [:test]
41
+ end
42
+
43
+ it "returns running actors" do
44
+ subject.running.should be_empty
45
+
46
+ first = subject.within do
47
+ TestActor.new
48
+ end
49
+
50
+ second = subject.within do
51
+ TestActor.new
52
+ end
53
+
54
+ subject.running.should == [first, second]
55
+ end
56
+
57
+ it "shuts down" do
58
+ subject.shutdown
59
+
60
+ lambda { subject.get_thread }.
61
+ should raise_error("Thread pool is not running")
62
+ end
63
+
64
+ it "warns nicely when no actor system is started" do
65
+ lambda { TestActor.new }.
66
+ should raise_error("Celluloid is not yet started; use Celluloid.boot")
67
+ end
68
+
69
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Blocks" do
3
+ describe "Blocks", actor_system: :global do
4
4
  class MyBlockActor
5
5
  include Celluloid
6
6
 
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::SyncCall do
3
+ describe Celluloid::SyncCall, actor_system: :global do
4
4
  class CallExampleActor
5
5
  include Celluloid
6
6
 
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::Condition do
3
+ describe Celluloid::Condition, actor_system: :global do
4
4
  class ConditionExample
5
5
  include Celluloid
6
6
 
@@ -17,10 +17,10 @@ describe Celluloid::Condition do
17
17
  condition.signal value
18
18
  end
19
19
 
20
- def wait_for_condition
20
+ def wait_for_condition(timeout = nil)
21
21
  @waiting = true
22
22
  begin
23
- value = @condition.wait
23
+ value = @condition.wait(timeout)
24
24
  @signaled_times += 1
25
25
  ensure
26
26
  @waiting = false
@@ -62,4 +62,15 @@ describe Celluloid::Condition do
62
62
  actor.async.signal_condition condition, :value
63
63
  condition.wait.should eq(:value)
64
64
  end
65
+
66
+ it "times out inside normal Threads" do
67
+ condition = Celluloid::Condition.new
68
+ lambda { condition.wait(1) }.
69
+ should raise_error(Celluloid::ConditionError)
70
+ end
71
+
72
+ it "times out inside Tasks" do
73
+ lambda { actor.wait_for_condition(1) }.
74
+ should raise_error(Celluloid::ConditionError)
75
+ end
65
76
  end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::CPUCounter do
4
+ describe :cores do
5
+ it 'should return an integer' do
6
+ Celluloid::CPUCounter.cores.should be_kind_of(Fixnum)
7
+ end
8
+ end
9
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::FSM do
3
+ describe Celluloid::FSM, actor_system: :global do
4
4
  before :all do
5
5
  class TestMachine
6
6
  include Celluloid::FSM
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::Future do
3
+ describe Celluloid::Future, actor_system: :global do
4
4
  it "creates future objects that can be retrieved later" do
5
5
  future = Celluloid::Future.new { 40 + 2 }
6
6
  future.value.should == 42
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::Notifications do
3
+ describe Celluloid::Notifications, actor_system: :global do
4
4
  class Admirer
5
5
  include Celluloid
6
6
  include Celluloid::Notifications
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Celluloid.pool" do
3
+ describe "Celluloid.pool", actor_system: :global do
4
4
  class ExampleError < StandardError; end
5
5
 
6
6
  class MyWorker
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyActor; include Celluloid; end
4
+
5
+ class TestProbeClient
6
+ include Celluloid
7
+ include Celluloid::Notifications
8
+
9
+ attr_reader :buffer
10
+
11
+ def initialize()
12
+ @condition = Condition.new
13
+ subscribe(/celluloid\.events\..+/, :event_received)
14
+ @buffer = []
15
+ end
16
+
17
+ def wait
18
+ @condition.wait
19
+ end
20
+
21
+ def wait_event(topic, expected_actor1 = nil, expected_actor2 = nil)
22
+ loop do
23
+ wait
24
+ while ev = @buffer.shift()
25
+ if (ev[0] == topic) && (ev[1].mailbox.address == expected_actor1.mailbox.address) &&
26
+ (expected_actor2.nil? || (ev[2].mailbox.address == expected_actor2.mailbox.address) )
27
+ return ev
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def event_received(topic, args)
34
+ @buffer << [topic, args[0], args[1]]
35
+ @condition.signal
36
+ end
37
+ end
38
+
39
+ describe "Probe", actor_system: :global do
40
+ describe 'on boot' do
41
+ it 'should capture system actor spawn' do
42
+ client = TestProbeClient.new
43
+ Celluloid::Probe.run
44
+ create_events = []
45
+ received_named_events = {
46
+ :default_incident_reporter => nil,
47
+ :notifications_fanout => nil
48
+ }
49
+ # wait for the events we seek
50
+ Timeout.timeout(5) do
51
+ loop do
52
+ client.wait
53
+ while ev = client.buffer.shift
54
+ if ev[0] == 'celluloid.events.actor_created'
55
+ create_events << ev
56
+ elsif ev[0] == 'celluloid.events.actor_named'
57
+ if received_named_events.keys.include?(ev[1].name)
58
+ received_named_events[ev[1].name] = ev[1].mailbox.address
59
+ end
60
+ end
61
+ end
62
+ if received_named_events.all?{|_, v| v != nil }
63
+ break
64
+ end
65
+ end
66
+ end
67
+ received_named_events.all?{|_, v| v != nil }.should == true
68
+ # now check we got the create events for every actors
69
+ received_named_events.each do |_, mailbox_address|
70
+ found = create_events.detect{|_, aa| aa.mailbox.address == mailbox_address }
71
+ found.should_not == nil
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'after boot' do
77
+ it 'should send a notification when an actor is spawned' do
78
+ client = TestProbeClient.new
79
+ Celluloid::Probe.run
80
+ a = DummyActor.new
81
+ event = Timeout.timeout(5) do
82
+ client.wait_event('celluloid.events.actor_created', a)
83
+ end
84
+ event.should_not == nil
85
+ end
86
+
87
+ it 'should send a notification when an actor is named' do
88
+ client = TestProbeClient.new
89
+ Celluloid::Probe.run
90
+ a = DummyActor.new
91
+ Celluloid::Actor['a name'] = a
92
+ event = Timeout.timeout(5) do
93
+ client.wait_event('celluloid.events.actor_named', a)
94
+ end
95
+ event.should_not == nil
96
+ end
97
+
98
+ it 'should send a notification when actor dies' do
99
+ client = TestProbeClient.new
100
+ Celluloid::Probe.run
101
+ a = DummyActor.new
102
+ a.terminate
103
+ event = Timeout.timeout(5) do
104
+ client.wait_event('celluloid.events.actor_died', a)
105
+ end
106
+ event.should_not == nil
107
+ end
108
+
109
+ it 'should send a notification when actors are linked' do
110
+ client = TestProbeClient.new
111
+ Celluloid::Probe.run
112
+ a = DummyActor.new
113
+ b = DummyActor.new
114
+ a.link(b)
115
+ event = Timeout.timeout(5) do
116
+ client.wait_event('celluloid.events.actors_linked', a, b)
117
+ end
118
+ event.should_not == nil
119
+ end
120
+ end
121
+ end