celluloid 0.15.2 → 0.16.0.pre

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