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
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Future do
4
+ it "creates future objects that can be retrieved later" do
5
+ future = Celluloid::Future.new { 40 + 2 }
6
+ future.value.should == 42
7
+ end
8
+
9
+ it "passes arguments to future blocks" do
10
+ future = Celluloid::Future.new(40) { |n| n + 2 }
11
+ future.value.should == 42
12
+ end
13
+
14
+ it "reraises exceptions that occur when the value is retrieved" do
15
+ class ExampleError < StandardError; end
16
+
17
+ future = Celluloid::Future.new { raise ExampleError, "oh noes crash!" }
18
+ expect { future.value }.to raise_exception(ExampleError)
19
+ end
20
+
21
+ it "knows if it's got a value yet" do
22
+ future = Celluloid::Future.new { sleep Celluloid::TIMER_QUANTUM * 5 }
23
+ future.should_not be_ready
24
+ sleep Celluloid::TIMER_QUANTUM * 6
25
+ future.should be_ready
26
+ end
27
+
28
+ it "raises TimeoutError when the future times out" do
29
+ future = Celluloid::Future.new { sleep 2 }
30
+ expect { future.value(1) }.to raise_exception(Celluloid::TimeoutError)
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::InternalPool do
4
+ it "gets threads from the pool" do
5
+ subject.get { sleep 1 }.should be_a Thread
6
+ end
7
+
8
+ it "puts threads back into the pool" do
9
+ subject.idle_size.should be_zero
10
+ subject.busy_size.should be_zero
11
+
12
+ queue = Queue.new
13
+ subject.get { queue.pop }
14
+
15
+ subject.idle_size.should be_zero
16
+ subject.busy_size.should eq 1
17
+
18
+ queue << nil
19
+ sleep 0.01 # hax
20
+
21
+ subject.idle_size.should eq 1
22
+ subject.busy_size.should eq 0
23
+ end
24
+
25
+ it "cleans thread locals from old threads" do
26
+ thread = subject.get { Thread.current[:foo] = :bar }
27
+
28
+ sleep 0.01 #hax
29
+ thread[:foo].should be_nil
30
+ end
31
+
32
+ it "doesn't fail if a third-party thread is spawned" do
33
+ subject.idle_size.should be_zero
34
+ subject.busy_size.should be_zero
35
+
36
+ subject.get { ::Thread.new { sleep 0.5 } }.should be_a(Celluloid::Thread)
37
+
38
+ sleep 0.01 # hax
39
+
40
+ subject.idle_size.should eq 1
41
+ subject.busy_size.should eq 0
42
+ end
43
+
44
+ it "doesn't leak dead threads" do
45
+ subject.max_idle = 0 # Instruct the pool to immediately shut down the thread.
46
+ subject.get { true }.should be_a(Celluloid::Thread)
47
+
48
+ sleep 0.01 # hax
49
+
50
+ subject.to_a.should have(0).items
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Links do
4
+ subject { Celluloid::Links.new }
5
+
6
+ let(:mailbox_mock) do
7
+ Class.new(Array) do
8
+ attr_reader :address
9
+ def initialize address
10
+ @address = address
11
+ end
12
+ end
13
+ end
14
+
15
+ let(:first_actor) do
16
+ Struct.new(:mailbox).new(mailbox_mock.new('foo123'))
17
+ end
18
+
19
+ let(:second_actor) do
20
+ Struct.new(:mailbox).new(mailbox_mock.new('bar456'))
21
+ end
22
+
23
+ it 'is Enumerable' do
24
+ subject.should be_an(Enumerable)
25
+ end
26
+
27
+ it 'adds actors by their mailbox address' do
28
+ subject.include?(first_actor).should be_false
29
+ subject << first_actor
30
+ subject.include?(first_actor).should be_true
31
+ end
32
+
33
+ it 'removes actors by their mailbox address' do
34
+ subject << first_actor
35
+ subject.include?(first_actor).should be_true
36
+ subject.delete first_actor
37
+ subject.include?(first_actor).should be_false
38
+ end
39
+
40
+ it 'iterates over all actors' do
41
+ subject << first_actor
42
+ subject << second_actor
43
+ subject.inject([]) { |all, a| all << a }.should == [first_actor, second_actor]
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::RingBuffer do
4
+ subject { Celluloid::RingBuffer.new(2) }
5
+
6
+ it { should be_empty }
7
+ it { should_not be_full }
8
+
9
+ it 'should push and shift' do
10
+ subject.push('foo')
11
+ subject.push('foo2')
12
+ subject.shift.should eq('foo')
13
+ subject.shift.should eq('foo2')
14
+ end
15
+
16
+ it 'should push past the end' do
17
+ subject.push('foo')
18
+ subject.push('foo2')
19
+ subject.push('foo3')
20
+ subject.should be_full
21
+ end
22
+
23
+ it 'should shift the most recent' do
24
+ (1..5).each { |i| subject.push(i) }
25
+ subject.shift.should be 4
26
+ subject.shift.should be 5
27
+ subject.shift.should be_nil
28
+ end
29
+
30
+ it 'should return nil when shifting empty' do
31
+ subject.should be_empty
32
+ subject.shift.should be_nil
33
+ end
34
+
35
+ it 'should be thread-safe' do
36
+ #TODO how to test?
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Mailbox do
4
+ it_behaves_like "a Celluloid Mailbox"
5
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Notifications do
4
+ class Admirer
5
+ include Celluloid
6
+ include Celluloid::Notifications
7
+
8
+ attr_reader :mourning
9
+ attr_reader :mourning_count
10
+
11
+ def someone_died(topic, name)
12
+ @mourning = name
13
+ @mourning_count ||= 0
14
+ @mourning_count += 1
15
+ end
16
+ end
17
+
18
+ class President
19
+ include Celluloid
20
+ include Celluloid::Notifications
21
+
22
+ def die
23
+ publish("death", "Mr. President")
24
+ end
25
+ end
26
+
27
+ it 'notifies relevant subscribers' do
28
+ marilyn = Admirer.new
29
+ jackie = Admirer.new
30
+
31
+ marilyn.subscribe("death", :someone_died)
32
+ jackie.subscribe("alive", :someone_died)
33
+
34
+ president = President.new
35
+
36
+ president.die
37
+ marilyn.mourning.should eq("Mr. President")
38
+ jackie.mourning.should_not eq("Mr. President")
39
+ end
40
+
41
+ it 'allows multiple subscriptions from the same actor' do
42
+ marilyn = Admirer.new
43
+
44
+ marilyn.subscribe("death", :someone_died)
45
+ marilyn.subscribe("death", :someone_died)
46
+
47
+ president = President.new
48
+
49
+ president.die
50
+ marilyn.mourning_count.should be(2)
51
+ end
52
+
53
+
54
+ it 'notifies subscribers' do
55
+ marilyn = Admirer.new
56
+ jackie = Admirer.new
57
+
58
+ marilyn.subscribe("death", :someone_died)
59
+ jackie.subscribe("death", :someone_died)
60
+
61
+ president = President.new
62
+
63
+ president.die
64
+ marilyn.mourning.should eq("Mr. President")
65
+ jackie.mourning.should eq("Mr. President")
66
+ end
67
+
68
+ it 'publishes even if there are no subscribers' do
69
+ president = President.new
70
+ president.die
71
+ end
72
+
73
+ it 'allows regex subscriptions' do
74
+ marilyn = Admirer.new
75
+
76
+ marilyn.subscribe(/(death|assassination)/, :someone_died)
77
+
78
+ president = President.new
79
+ president.die
80
+ marilyn.mourning.should eq("Mr. President")
81
+ end
82
+
83
+ it 'allows unsubscribing' do
84
+ marilyn = Admirer.new
85
+
86
+ subscription = marilyn.subscribe("death", :someone_died)
87
+ marilyn.unsubscribe(subscription)
88
+
89
+ president = President.new
90
+ president.die
91
+ marilyn.mourning.should be_nil
92
+ end
93
+
94
+ it 'prunes dead subscriptions' do
95
+ marilyn = Admirer.new
96
+ jackie = Admirer.new
97
+
98
+ marilyn.subscribe("death", :someone_died)
99
+ jackie.subscribe("death", :someone_died)
100
+
101
+ listeners = Celluloid::Notifications.notifier.listeners_for("death").size
102
+ marilyn.terminate
103
+ after_listeners = Celluloid::Notifications.notifier.listeners_for("death").size
104
+
105
+ after_listeners.should == listeners - 1
106
+ end
107
+
108
+ it 'prunes multiple subscriptions from a dead actor' do
109
+ marilyn = Admirer.new
110
+
111
+ marilyn.subscribe("death", :someone_died)
112
+ marilyn.subscribe("death", :someone_died)
113
+
114
+ listeners = Celluloid::Notifications.notifier.listeners_for("death").size
115
+ marilyn.terminate
116
+ after_listeners = Celluloid::Notifications.notifier.listeners_for("death").size
117
+
118
+ after_listeners.should eq(listeners - 2)
119
+ end
120
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Celluloid.pool" do
4
+ class ExampleError < StandardError; end
5
+
6
+ class MyWorker
7
+ include Celluloid
8
+
9
+ def process(queue = nil)
10
+ if queue
11
+ queue << :done
12
+ else
13
+ :done
14
+ end
15
+ end
16
+
17
+ def crash
18
+ raise ExampleError, "zomgcrash"
19
+ end
20
+ end
21
+
22
+ subject { MyWorker.pool }
23
+
24
+ it "processes work units synchronously" do
25
+ subject.process.should be :done
26
+ end
27
+
28
+ it "processes work units asynchronously" do
29
+ queue = Queue.new
30
+ subject.async.process(queue)
31
+ queue.pop.should be :done
32
+ end
33
+
34
+ it "handles crashes" do
35
+ expect { subject.crash }.to raise_error(ExampleError)
36
+ subject.process.should be :done
37
+ end
38
+
39
+ it "uses a fixed-sized number of threads" do
40
+ subject # eagerly evaluate the pool to spawn it
41
+
42
+ actors = Celluloid::Actor.all
43
+ 100.times.map { subject.future(:process) }.map(&:value)
44
+
45
+ new_actors = Celluloid::Actor.all - actors
46
+ new_actors.should eq []
47
+ end
48
+
49
+ it "terminates" do
50
+ expect { subject.terminate }.to_not raise_exception
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Properties do
4
+ let(:default_value) { 42 }
5
+ let(:changed_value) { 43 }
6
+
7
+ let(:example_class) do
8
+ Class.new do
9
+ extend Celluloid::Properties
10
+ property :baz, :default => 42
11
+ end
12
+ end
13
+
14
+ let(:example_subclass) do
15
+ Class.new(example_class)
16
+ end
17
+
18
+ let(:example_subclass_subclass) do
19
+ Class.new(example_subclass)
20
+ end
21
+
22
+ it "adds properties to classes" do
23
+ example_class.baz.should eq default_value
24
+ example_class.baz changed_value
25
+ example_class.baz.should eq changed_value
26
+ end
27
+
28
+ it "allows properties to be inherited" do
29
+ example_subclass.baz.should eq default_value
30
+ example_subclass.baz changed_value
31
+ example_subclass.baz.should eq changed_value
32
+ example_class.baz.should eq default_value
33
+ end
34
+
35
+ it "allows properties to be deeply inherited" do
36
+ example_subclass_subclass.baz.should eq default_value
37
+ example_subclass_subclass.baz changed_value
38
+ example_subclass_subclass.baz.should eq changed_value
39
+ example_subclass.baz.should eq default_value
40
+ example_class.baz.should eq default_value
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Registry do
4
+ class Marilyn
5
+ include Celluloid
6
+
7
+ def sing_for(person)
8
+ "o/~ Happy birthday, #{person}"
9
+ end
10
+ end
11
+
12
+ it "registers Actors" do
13
+ Celluloid::Actor[:marilyn] = Marilyn.new
14
+ Celluloid::Actor[:marilyn].sing_for("Mr. President").should == "o/~ Happy birthday, Mr. President"
15
+ end
16
+
17
+ it "refuses to register non-Actors" do
18
+ expect do
19
+ Celluloid::Actor[:impostor] = Object.new
20
+ end.to raise_error TypeError
21
+ end
22
+
23
+ it "lists all registered actors" do
24
+ Celluloid::Actor[:marilyn] = Marilyn.new
25
+ Celluloid::Actor.registered.should include :marilyn
26
+ end
27
+
28
+ it "knows its name once registered" do
29
+ Celluloid::Actor[:marilyn] = Marilyn.new
30
+ Celluloid::Actor[:marilyn].name.should == :marilyn
31
+ end
32
+
33
+ describe :delete do
34
+ before do
35
+ Celluloid::Actor[:marilyn] ||= Marilyn.new
36
+ end
37
+
38
+ it "removes reference to actors' name from the registry" do
39
+ Celluloid::Registry.root.delete(:marilyn)
40
+ Celluloid::Actor.registered.should_not include :marilyn
41
+ end
42
+
43
+ it "returns actor removed from the registry" do
44
+ rval = Celluloid::Registry.root.delete(:marilyn)
45
+ rval.should be_kind_of(Marilyn)
46
+ end
47
+ end
48
+
49
+ describe :clear do
50
+ it "should return a hash of registered actors and remove them from the registry" do
51
+ Celluloid::Actor[:marilyn] ||= Marilyn.new
52
+ rval = Celluloid::Actor.clear_registry
53
+ begin
54
+ rval.should be_kind_of(Hash)
55
+ rval.should have_key(:marilyn)
56
+ rval[:marilyn].wrapped_object.should be_instance_of(Marilyn)
57
+ Celluloid::Actor.registered.should be_empty
58
+ ensure
59
+ # Repopulate the registry once we're done
60
+ rval.each { |key, actor| Celluloid::Actor[key] = actor }
61
+ end
62
+ end
63
+ end
64
+ end