celluloid 0.15.2 → 0.16.0

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/LICENSE.txt +20 -0
  3. data/README.md +25 -2
  4. data/lib/celluloid/actor.rb +88 -147
  5. data/lib/celluloid/actor_system.rb +107 -0
  6. data/lib/celluloid/call_chain.rb +1 -1
  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 +28 -18
  11. data/lib/celluloid/evented_mailbox.rb +10 -16
  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 +49 -40
  16. data/lib/celluloid/logger.rb +30 -0
  17. data/lib/celluloid/logging/incident_logger.rb +1 -1
  18. data/lib/celluloid/mailbox.rb +35 -31
  19. data/lib/celluloid/method.rb +8 -0
  20. data/lib/celluloid/pool_manager.rb +19 -2
  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 +5 -13
  27. data/lib/celluloid/registry.rb +1 -8
  28. data/{spec/support → lib/celluloid/rspec}/actor_examples.rb +58 -15
  29. data/{spec/support → lib/celluloid/rspec}/mailbox_examples.rb +9 -3
  30. data/{spec/support → lib/celluloid/rspec}/task_examples.rb +2 -0
  31. data/lib/celluloid/rspec.rb +4 -3
  32. data/lib/celluloid/stack_dump.rb +34 -11
  33. data/lib/celluloid/supervision_group.rb +26 -14
  34. data/lib/celluloid/tasks/task_fiber.rb +6 -0
  35. data/lib/celluloid/tasks/task_thread.rb +2 -1
  36. data/lib/celluloid/tasks.rb +28 -9
  37. data/lib/celluloid/thread_handle.rb +2 -2
  38. data/lib/celluloid.rb +68 -73
  39. data/spec/celluloid/actor_spec.rb +1 -1
  40. data/spec/celluloid/actor_system_spec.rb +69 -0
  41. data/spec/celluloid/block_spec.rb +1 -1
  42. data/spec/celluloid/calls_spec.rb +1 -1
  43. data/spec/celluloid/condition_spec.rb +14 -3
  44. data/spec/celluloid/cpu_counter_spec.rb +82 -0
  45. data/spec/celluloid/fsm_spec.rb +1 -1
  46. data/spec/celluloid/future_spec.rb +1 -1
  47. data/spec/celluloid/notifications_spec.rb +1 -1
  48. data/spec/celluloid/pool_spec.rb +34 -1
  49. data/spec/celluloid/probe_spec.rb +121 -0
  50. data/spec/celluloid/registry_spec.rb +6 -6
  51. data/spec/celluloid/stack_dump_spec.rb +37 -8
  52. data/spec/celluloid/supervision_group_spec.rb +7 -1
  53. data/spec/celluloid/supervisor_spec.rb +12 -1
  54. data/spec/celluloid/tasks/task_fiber_spec.rb +1 -1
  55. data/spec/celluloid/tasks/task_thread_spec.rb +1 -1
  56. data/spec/celluloid/thread_handle_spec.rb +7 -3
  57. data/spec/celluloid/timer_spec.rb +48 -0
  58. data/spec/spec_helper.rb +20 -7
  59. metadata +51 -26
  60. /data/{spec/support → lib/celluloid/rspec}/example_actor_class.rb +0 -0
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::CPUCounter do
4
+ describe :cores do
5
+ subject { described_class.cores }
6
+
7
+ let(:num_cores) { 1024 }
8
+
9
+ before do
10
+ described_class.stub(:`) { fail 'backtick stub called' }
11
+ ::IO.stub(:open).and_raise('IO.open stub called!')
12
+ described_class.instance_variable_set('@cores', nil)
13
+ end
14
+
15
+ after { ENV['NUMBER_OF_PROCESSORS'] = nil }
16
+
17
+ context 'from valid env value' do
18
+ before { ENV['NUMBER_OF_PROCESSORS'] = num_cores.to_s }
19
+ it { should eq num_cores }
20
+ end
21
+
22
+ context 'from invalid env value' do
23
+ before { ENV['NUMBER_OF_PROCESSORS'] = '' }
24
+ specify { expect { subject }.to raise_error(ArgumentError) }
25
+ end
26
+
27
+ context 'with no env value' do
28
+ before { ENV['NUMBER_OF_PROCESSORS'] = nil }
29
+
30
+ context 'when /sys/devices/system/cpu/present exists' do
31
+ before do
32
+ ::IO.should_receive(:read).with('/sys/devices/system/cpu/present')
33
+ .and_return("dunno-whatever-#{num_cores - 1}")
34
+ end
35
+ it { should eq num_cores }
36
+ end
37
+
38
+ context 'when /sys/devices/system/cpu/present does NOT exist' do
39
+ before do
40
+ ::IO.should_receive(:read).with('/sys/devices/system/cpu/present')
41
+ .and_raise(Errno::ENOENT)
42
+ end
43
+
44
+ context 'when /sys/devices/system/cpu/cpu* files exist' do
45
+ before do
46
+ cpu_entries = (1..num_cores).map { |n| "cpu#{n}" }
47
+ cpu_entries << 'non-cpu-entry-to-ignore'
48
+ Dir.should_receive(:[]).with('/sys/devices/system/cpu/cpu*')
49
+ .and_return(cpu_entries)
50
+ end
51
+ it { should eq num_cores }
52
+ end
53
+
54
+ context 'when /sys/devices/system/cpu/cpu* files DO NOT exist' do
55
+ before do
56
+ Dir.should_receive(:[]).with('/sys/devices/system/cpu/cpu*')
57
+ .and_return([])
58
+ end
59
+
60
+ context 'when sysctl blows up' do
61
+ before { described_class.stub(:`).and_raise(Errno::EINTR) }
62
+ specify { expect { subject }.to raise_error }
63
+ end
64
+
65
+ context 'when sysctl fails' do
66
+ before { described_class.stub(:`).and_return(`false`) }
67
+ it { should be nil }
68
+ end
69
+
70
+ context 'when sysctl succeeds' do
71
+ before do
72
+ described_class.should_receive(:`).with('sysctl -n hw.ncpu')
73
+ .and_return(num_cores.to_s)
74
+ `true`
75
+ end
76
+ it { should eq num_cores }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ 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
@@ -14,11 +14,23 @@ describe "Celluloid.pool" do
14
14
  end
15
15
  end
16
16
 
17
+ def sleepy_work
18
+ t = Time.now.to_f
19
+ sleep 0.25
20
+ t
21
+ end
22
+
17
23
  def crash
18
24
  raise ExampleError, "zomgcrash"
19
25
  end
20
26
  end
21
27
 
28
+ def test_concurrency_of(pool)
29
+ baseline = Time.now.to_f
30
+ values = 10.times.map { pool.future.sleepy_work }.map(&:value)
31
+ values.select {|t| t - baseline < 0.1 }.length
32
+ end
33
+
22
34
  subject { MyWorker.pool }
23
35
 
24
36
  it "processes work units synchronously" do
@@ -56,4 +68,25 @@ describe "Celluloid.pool" do
56
68
  end
57
69
  futures.map(&:value)
58
70
  end
71
+
72
+ context "#size=" do
73
+ subject { MyWorker.pool size: 4 }
74
+
75
+ it "should adjust the pool size up", pending: 'flaky' do
76
+ expect(test_concurrency_of(subject)).to eq(4)
77
+
78
+ subject.size = 6
79
+ subject.size.should == 6
80
+
81
+ test_concurrency_of(subject).should == 6
82
+ end
83
+
84
+ it "should adjust the pool size down" do
85
+ test_concurrency_of(subject).should == 4
86
+
87
+ subject.size = 2
88
+ subject.size.should == 2
89
+ test_concurrency_of(subject).should == 2
90
+ end
91
+ end
59
92
  end
@@ -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
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::Registry do
3
+ describe Celluloid::Registry, actor_system: :global do
4
4
  class Marilyn
5
5
  include Celluloid
6
6
 
@@ -27,21 +27,21 @@ describe Celluloid::Registry do
27
27
 
28
28
  it "knows its name once registered" do
29
29
  Celluloid::Actor[:marilyn] = Marilyn.new
30
- Celluloid::Actor[:marilyn].name.should == :marilyn
30
+ Celluloid::Actor[:marilyn].registered_name.should == :marilyn
31
31
  end
32
32
 
33
33
  describe :delete do
34
34
  before do
35
35
  Celluloid::Actor[:marilyn] ||= Marilyn.new
36
36
  end
37
-
37
+
38
38
  it "removes reference to actors' name from the registry" do
39
- Celluloid::Registry.root.delete(:marilyn)
39
+ Celluloid::Actor.delete(:marilyn)
40
40
  Celluloid::Actor.registered.should_not include :marilyn
41
41
  end
42
-
42
+
43
43
  it "returns actor removed from the registry" do
44
- rval = Celluloid::Registry.root.delete(:marilyn)
44
+ rval = Celluloid::Actor.delete(:marilyn)
45
45
  rval.should be_kind_of(Marilyn)
46
46
  end
47
47
  end
@@ -1,6 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Celluloid::StackDump do
4
+ let(:actor_system) do
5
+ Celluloid::ActorSystem.new
6
+ end
7
+
8
+ subject do
9
+ actor_system.stack_dump
10
+ end
11
+
4
12
  class BlockingActor
5
13
  include Celluloid
6
14
 
@@ -14,22 +22,43 @@ describe Celluloid::StackDump do
14
22
  actor_klass = Class.new(BlockingActor) do
15
23
  task_class task_klass
16
24
  end
17
- actor = actor_klass.new
25
+ actor = actor_system.within do
26
+ actor_klass.new
27
+ end
18
28
  actor.async.blocking
19
29
  end
20
30
 
21
- Celluloid.internal_pool.get do
22
- Thread.current.role = :testing
31
+ @active_thread = actor_system.get_thread do
23
32
  sleep
24
33
  end
34
+ @active_thread.role = :other_thing
35
+ @idle_thread = actor_system.get_thread do
36
+ end
37
+
38
+ sleep 0.01
25
39
  end
26
40
 
27
- it 'should include all actors' do
28
- subject.actors.size.should == Celluloid::Actor.all.size
41
+ describe '#actors' do
42
+ it 'should include all actors' do
43
+ subject.actors.size.should == actor_system.running.size
44
+ end
29
45
  end
30
46
 
31
- it 'should include threads that are not actors' do
32
- pending "bugs"
33
- subject.threads.size.should == 2
47
+ describe '#threads' do
48
+ it 'should include threads that are not actors', pending: 'flaky' do
49
+ expect(subject.threads.size).to eq(3)
50
+ end
51
+
52
+ it 'should include idle threads' do
53
+ subject.threads.map(&:thread_id).should include(@idle_thread.object_id)
54
+ end
55
+
56
+ it 'should include threads checked out of the pool for roles other than :actor' do
57
+ subject.threads.map(&:thread_id).should include(@active_thread.object_id)
58
+ end
59
+
60
+ it 'should have the correct roles', pending: 'flaky' do
61
+ expect(subject.threads.map(&:role)).to include(nil, :other_thing, :task)
62
+ end
34
63
  end
35
64
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::SupervisionGroup do
3
+ describe Celluloid::SupervisionGroup, actor_system: :global do
4
4
  before :all do
5
5
  class MyActor
6
6
  include Celluloid
@@ -55,5 +55,11 @@ describe Celluloid::SupervisionGroup do
55
55
  Celluloid::Actor[:example_pool].args.should eq ['foo']
56
56
  Celluloid::Actor[:example_pool].size.should be 3
57
57
  end
58
+
59
+ it "allows external access to the internal registry" do
60
+ supervisor = MyGroup.run!
61
+
62
+ supervisor[:example].should be_a MyActor
63
+ end
58
64
  end
59
65
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::Supervisor do
3
+ describe Celluloid::Supervisor, actor_system: :global do
4
4
  class SubordinateDead < StandardError; end
5
5
 
6
6
  class Subordinate
@@ -89,4 +89,15 @@ describe Celluloid::Supervisor do
89
89
  new_subordinate.should_not eq subordinate
90
90
  new_subordinate.state.should be(:working)
91
91
  end
92
+
93
+ it "removes an actor if it terminates cleanly" do
94
+ supervisor = Subordinate.supervise(:working)
95
+ subordinate = supervisor.actors.first
96
+
97
+ supervisor.actors.should == [subordinate]
98
+
99
+ subordinate.terminate
100
+
101
+ supervisor.actors.should be_empty
102
+ end
92
103
  end
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::TaskFiber do
3
+ describe Celluloid::TaskFiber, actor_system: :within do
4
4
  it_behaves_like "a Celluloid Task", Celluloid::TaskFiber
5
5
  end
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Celluloid::TaskThread do
3
+ describe Celluloid::TaskThread, actor_system: :within do
4
4
  it_behaves_like "a Celluloid Task", Celluloid::TaskThread
5
5
  end
@@ -1,9 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Celluloid::ThreadHandle do
4
+ let(:actor_system) do
5
+ Celluloid::ActorSystem.new
6
+ end
7
+
4
8
  it "knows thread liveliness" do
5
9
  queue = Queue.new
6
- handle = Celluloid::ThreadHandle.new { queue.pop }
10
+ handle = Celluloid::ThreadHandle.new(actor_system) { queue.pop }
7
11
  handle.should be_alive
8
12
 
9
13
  queue << :die
@@ -13,10 +17,10 @@ describe Celluloid::ThreadHandle do
13
17
  end
14
18
 
15
19
  it "joins to thread handles" do
16
- Celluloid::ThreadHandle.new { sleep 0.01 }.join
20
+ Celluloid::ThreadHandle.new(actor_system) { sleep 0.01 }.join
17
21
  end
18
22
 
19
23
  it "supports passing a role" do
20
- Celluloid::ThreadHandle.new(:useful) { Thread.current.role.should == :useful }.join
24
+ Celluloid::ThreadHandle.new(actor_system, :useful) { Thread.current.role.should == :useful }.join
21
25
  end
22
26
  end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ class EveryActor
4
+ include Celluloid
5
+
6
+ def initialize
7
+ @trace = []
8
+ @times = []
9
+ @start = Time.now
10
+
11
+ every(1) { log(1) }
12
+ every(2) { log(2) }
13
+ every(1) { log(11) }
14
+ every(2) { log(22) }
15
+ end
16
+
17
+ def log(t)
18
+ @trace << t
19
+
20
+ offset = Time.now - @start
21
+ @times << offset
22
+
23
+ # puts "log(#{t}) @ #{offset}"
24
+ end
25
+
26
+ attr :trace
27
+ attr :times
28
+ end
29
+
30
+ describe Celluloid::Actor do
31
+ it "run every(t) task several times" do
32
+ Celluloid.boot
33
+
34
+ every_actor = EveryActor.new
35
+
36
+ sleep 5.5
37
+
38
+ times = every_actor.times
39
+ trace = every_actor.trace
40
+
41
+ Celluloid.shutdown
42
+
43
+ expect(trace.count(1)).to be == 5
44
+ expect(trace.count(11)).to be == 5
45
+ expect(trace.count(2)).to be == 2
46
+ expect(trace.count(22)).to be == 2
47
+ end
48
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,11 +4,12 @@ Coveralls.wear!
4
4
  require 'rubygems'
5
5
  require 'bundler/setup'
6
6
  require 'celluloid/rspec'
7
+ require 'celluloid/probe'
7
8
 
8
9
  logfile = File.open(File.expand_path("../../log/test.log", __FILE__), 'a')
9
10
  logfile.sync = true
10
11
 
11
- logger = Celluloid.logger = Logger.new(logfile)
12
+ Celluloid.logger = Logger.new(logfile)
12
13
 
13
14
  Celluloid.shutdown_timeout = 1
14
15
 
@@ -18,14 +19,26 @@ RSpec.configure do |config|
18
19
  config.filter_run :focus => true
19
20
  config.run_all_when_everything_filtered = true
20
21
 
21
- config.before do
22
- Celluloid.logger = logger
23
- if Celluloid.running?
24
- Celluloid.shutdown
25
- sleep 0.01
26
- Celluloid.internal_pool.assert_inactive
22
+ config.around do |ex|
23
+ Celluloid.actor_system = nil
24
+ Thread.list.each do |thread|
25
+ next if thread == Thread.current
26
+ thread.kill
27
27
  end
28
28
 
29
+ ex.run
30
+ end
31
+
32
+ config.around actor_system: :global do |ex|
29
33
  Celluloid.boot
34
+ ex.run
35
+ Celluloid.shutdown
36
+ end
37
+
38
+ config.around actor_system: :within do |ex|
39
+ Celluloid::ActorSystem.new.within do
40
+ ex.run
41
+ end
30
42
  end
43
+
31
44
  end