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,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::StackDump do
4
+ class BlockingActor
5
+ include Celluloid
6
+
7
+ def blocking
8
+ Kernel.sleep
9
+ end
10
+ end
11
+
12
+ before(:each) do
13
+ [Celluloid::TaskFiber, Celluloid::TaskThread].each do |task_klass|
14
+ actor_klass = Class.new(BlockingActor) do
15
+ task_class task_klass
16
+ end
17
+ actor = actor_klass.new
18
+ actor.async.blocking
19
+ end
20
+
21
+ Celluloid.internal_pool.get do
22
+ Thread.current.role = :testing
23
+ sleep
24
+ end
25
+ end
26
+
27
+ it 'should include all actors' do
28
+ subject.actors.size.should == Celluloid::Actor.all.size
29
+ end
30
+
31
+ it 'should include threads that are not actors' do
32
+ pending "bugs"
33
+ subject.threads.size.should == 2
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::SupervisionGroup do
4
+ before :all do
5
+ class MyActor
6
+ include Celluloid
7
+
8
+ def running?; :yep; end
9
+ end
10
+
11
+ class MyGroup < Celluloid::SupervisionGroup
12
+ supervise MyActor, :as => :example
13
+ end
14
+ end
15
+
16
+ it "runs applications" do
17
+ MyGroup.run!
18
+ sleep 0.01 # startup time hax
19
+
20
+ Celluloid::Actor[:example].should be_running
21
+ end
22
+
23
+ it "accepts a private actor registry" do
24
+ my_registry = Celluloid::Registry.new
25
+ MyGroup.run!(my_registry)
26
+ sleep 0.01
27
+
28
+ my_registry[:example].should be_running
29
+ end
30
+
31
+ context "pool" do
32
+ before :all do
33
+ class MyActor
34
+ attr_reader :args
35
+ def initialize *args
36
+ @args = *args
37
+ end
38
+ end
39
+ class MyGroup
40
+ pool MyActor, :as => :example_pool, :args => 'foo', :size => 3
41
+ end
42
+ end
43
+
44
+ it "runs applications and passes pool options and actor args" do
45
+ MyGroup.run!
46
+ sleep 0.001 # startup time hax
47
+
48
+ Celluloid::Actor[:example_pool].should be_running
49
+ Celluloid::Actor[:example_pool].args.should eq ['foo']
50
+ Celluloid::Actor[:example_pool].size.should be 3
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Supervisor do
4
+ class SubordinateDead < StandardError; end
5
+
6
+ class Subordinate
7
+ include Celluloid
8
+ attr_reader :state
9
+
10
+ def initialize(state)
11
+ @state = state
12
+ end
13
+
14
+ def crack_the_whip
15
+ case @state
16
+ when :idle
17
+ @state = :working
18
+ else raise SubordinateDead, "the spec purposely crashed me :("
19
+ end
20
+ end
21
+ end
22
+
23
+ it "restarts actors when they die" do
24
+ supervisor = Celluloid::Supervisor.supervise(Subordinate, :idle)
25
+ subordinate = supervisor.actors.first
26
+ subordinate.state.should be(:idle)
27
+
28
+ subordinate.crack_the_whip
29
+ subordinate.state.should be(:working)
30
+
31
+ expect do
32
+ subordinate.crack_the_whip
33
+ end.to raise_exception(SubordinateDead)
34
+ sleep 0.1 # hax to prevent race :(
35
+ subordinate.should_not be_alive
36
+
37
+ new_subordinate = supervisor.actors.first
38
+ new_subordinate.should_not eq subordinate
39
+ new_subordinate.state.should eq :idle
40
+ end
41
+
42
+ it "registers actors and reregisters them when they die" do
43
+ Celluloid::Supervisor.supervise_as(:subordinate, Subordinate, :idle)
44
+ subordinate = Celluloid::Actor[:subordinate]
45
+ subordinate.state.should be(:idle)
46
+
47
+ subordinate.crack_the_whip
48
+ subordinate.state.should be(:working)
49
+
50
+ expect do
51
+ subordinate.crack_the_whip
52
+ end.to raise_exception(SubordinateDead)
53
+ sleep 0.1 # hax to prevent race :(
54
+ subordinate.should_not be_alive
55
+
56
+ new_subordinate = Celluloid::Actor[:subordinate]
57
+ new_subordinate.should_not eq subordinate
58
+ new_subordinate.state.should eq :idle
59
+ end
60
+
61
+ it "creates supervisors via Actor.supervise" do
62
+ supervisor = Subordinate.supervise(:working)
63
+ subordinate = supervisor.actors.first
64
+ subordinate.state.should be(:working)
65
+
66
+ expect do
67
+ subordinate.crack_the_whip
68
+ end.to raise_exception(SubordinateDead)
69
+ sleep 0.1 # hax to prevent race :(
70
+ subordinate.should_not be_alive
71
+
72
+ new_subordinate = supervisor.actors.first
73
+ new_subordinate.should_not eq subordinate
74
+ new_subordinate.state.should eq :working
75
+ end
76
+
77
+ it "creates supervisors and registers actors via Actor.supervise_as" do
78
+ supervisor = Subordinate.supervise_as(:subordinate, :working)
79
+ subordinate = Celluloid::Actor[:subordinate]
80
+ subordinate.state.should be(:working)
81
+
82
+ expect do
83
+ subordinate.crack_the_whip
84
+ end.to raise_exception(SubordinateDead)
85
+ sleep 0.1 # hax to prevent race :(
86
+ subordinate.should_not be_alive
87
+
88
+ new_subordinate = supervisor.actors.first
89
+ new_subordinate.should_not eq subordinate
90
+ new_subordinate.state.should be(:working)
91
+ end
92
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::TaskFiber do
4
+ it_behaves_like "a Celluloid Task", Celluloid::TaskFiber
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::TaskThread do
4
+ it_behaves_like "a Celluloid Task", Celluloid::TaskThread
5
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::ThreadHandle do
4
+ it "knows thread liveliness" do
5
+ queue = Queue.new
6
+ handle = Celluloid::ThreadHandle.new { queue.pop }
7
+ handle.should be_alive
8
+
9
+ queue << :die
10
+
11
+ sleep 0.01 # hax
12
+ handle.should_not be_alive
13
+ end
14
+
15
+ it "joins to thread handles" do
16
+ Celluloid::ThreadHandle.new { sleep 0.01 }.join
17
+ end
18
+
19
+ it "supports passing a role" do
20
+ Celluloid::ThreadHandle.new(:useful) { Thread.current.role.should == :useful }.join
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::UUID do
4
+ U = Celluloid::UUID
5
+
6
+ it "generates unique IDs across the BLOCK_SIZE boundary" do
7
+ upper_bound = U::BLOCK_SIZE * 2 + 10
8
+ uuids = (1..upper_bound).map{ U.generate }
9
+ uuids.size.should == uuids.uniq.size
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+ require 'celluloid'
7
+ require 'celluloid/rspec'
8
+
9
+ logfile = File.open(File.expand_path("../../log/test.log", __FILE__), 'a')
10
+ logfile.sync = true
11
+
12
+ logger = Celluloid.logger = Logger.new(logfile)
13
+
14
+ Celluloid.shutdown_timeout = 1
15
+
16
+ Dir['./spec/support/*.rb'].map {|f| require f }
17
+
18
+ RSpec.configure do |config|
19
+ config.filter_run :focus => true
20
+ config.run_all_when_everything_filtered = true
21
+
22
+ config.before do
23
+ Celluloid.logger = logger
24
+ Celluloid.shutdown
25
+ sleep 0.01
26
+
27
+ Celluloid.internal_pool.assert_inactive
28
+
29
+ Celluloid.boot
30
+ end
31
+ end
@@ -34,6 +34,11 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
34
34
  actor.object_id.should_not eq(Kernel.object_id)
35
35
  end
36
36
 
37
+ it "implements respond_to? correctly" do
38
+ actor = actor_class.new 'Troy McClure'
39
+ actor.should respond_to(:alive?)
40
+ end
41
+
37
42
  it "supports synchronous calls" do
38
43
  actor = actor_class.new "Troy McClure"
39
44
  actor.greet.should eq("Hi, I'm Troy McClure")
@@ -91,6 +96,36 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
91
96
  ponycopter.greet_by_proxy(actor).should eq("Hi, I'm a ponycopter!")
92
97
  end
93
98
 
99
+ it "detects recursion" do
100
+ klass1 = Class.new do
101
+ include included_module
102
+ task_class task_klass
103
+
104
+ def recursion_test(recurse_through = nil)
105
+ if recurse_through
106
+ recurse_through.recursion_thunk(Celluloid::Actor.current)
107
+ else
108
+ Celluloid.detect_recursion
109
+ end
110
+ end
111
+ end
112
+
113
+ klass2 = Class.new do
114
+ include included_module
115
+ task_class task_klass
116
+
117
+ def recursion_thunk(other)
118
+ other.recursion_test
119
+ end
120
+ end
121
+
122
+ actor1 = klass1.new
123
+ actor2 = klass2.new
124
+
125
+ actor1.recursion_test.should be_false
126
+ actor1.recursion_test(actor2).should be_true
127
+ end
128
+
94
129
  it "properly handles method_missing" do
95
130
  actor = actor_class.new "Method Missing"
96
131
  actor.should respond_to(:first)
@@ -103,6 +138,24 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
103
138
  actor.respond_to?(:zomg_private, true).should be_true
104
139
  end
105
140
 
141
+ it "warns about suspending the initialize" do
142
+ klass = Class.new do
143
+ include included_module
144
+ task_class task_klass
145
+
146
+ def initialize
147
+ sleep 0.1
148
+ end
149
+ end
150
+
151
+ Celluloid.logger = double.as_null_object
152
+ Celluloid.logger.should_receive(:warn).with(/Dangerously suspending task: type=:call, meta={:method_name=>:initialize}, status=:sleeping/)
153
+
154
+ actor = klass.new
155
+ actor.terminate
156
+ Celluloid::Actor.join(actor) unless defined?(JRUBY_VERSION)
157
+ end
158
+
106
159
  it "calls the user defined finalizer" do
107
160
  actor = actor_class.new "Mr. Bean"
108
161
  actor.wrapped_object.should_receive(:my_finalizer)
@@ -110,6 +163,26 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
110
163
  Celluloid::Actor.join(actor)
111
164
  end
112
165
 
166
+ it "warns about suspending the finalizer" do
167
+ klass = Class.new do
168
+ include included_module
169
+ task_class task_klass
170
+
171
+ finalizer :cleanup
172
+
173
+ def cleanup
174
+ sleep 0.1
175
+ end
176
+ end
177
+
178
+ Celluloid.logger = double.as_null_object
179
+ Celluloid.logger.should_receive(:warn).with(/Dangerously suspending task: type=:finalizer, meta={:method_name=>:cleanup}, status=:sleeping/)
180
+
181
+ actor = klass.new
182
+ actor.terminate
183
+ Celluloid::Actor.join(actor)
184
+ end
185
+
113
186
  it "supports async(:method) syntax for asynchronous calls" do
114
187
  actor = actor_class.new "Troy McClure"
115
188
  actor.async :change_name, "Charlie Sheen"
@@ -162,6 +235,27 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
162
235
  actor.inspect.should include('dead')
163
236
  end
164
237
 
238
+ it "supports recursive inspect with other actors" do
239
+ klass = Class.new do
240
+ include included_module
241
+ task_class task_klass
242
+
243
+ attr_accessor :other
244
+
245
+ def initialize(other = nil)
246
+ @other = other
247
+ end
248
+ end
249
+
250
+ itchy = klass.new
251
+ scratchy = klass.new(itchy)
252
+ itchy.other = scratchy
253
+
254
+ inspection = itchy.inspect
255
+ inspection.should match(/Celluloid::ActorProxy\(/)
256
+ inspection.should include("...")
257
+ end
258
+
165
259
  it "allows access to the wrapped object" do
166
260
  actor = actor_class.new "Troy McClure"
167
261
  actor.wrapped_object.should be_a actor_class
@@ -239,6 +333,8 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
239
333
  actor = actor_class.new "James Dean"
240
334
  actor.crash rescue nil
241
335
 
336
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
337
+
242
338
  expect do
243
339
  actor.greet
244
340
  end.to raise_exception(Celluloid::DeadActorError)
@@ -283,6 +379,14 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
283
379
  actor.should_not be_alive
284
380
  end
285
381
 
382
+ it "can be terminated by a SyncCall" do
383
+ actor = actor_class.new "Arnold Schwarzenegger"
384
+ actor.should be_alive
385
+ actor.shutdown
386
+ Celluloid::Actor.join(actor)
387
+ actor.should_not be_alive
388
+ end
389
+
286
390
  it "kills" do # THOU SHALT ALWAYS KILL
287
391
  actor = actor_class.new "Woody Harrelson"
288
392
  actor.should be_alive
@@ -308,6 +412,17 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
308
412
  actor.terminate!
309
413
  end.to raise_exception(Celluloid::DeadActorError, "actor already terminated")
310
414
  end
415
+
416
+ it "logs a warning when terminating tasks" do
417
+ Celluloid.logger = double.as_null_object
418
+ Celluloid.logger.should_receive(:warn).with("Terminating task: type=:call, meta={:method_name=>:sleepy}, status=:sleeping")
419
+
420
+ actor = actor_class.new "Arnold Schwarzenegger"
421
+ actor.async.sleepy 10
422
+ actor.greet # make sure the actor has started sleeping
423
+
424
+ actor.terminate
425
+ end
311
426
  end
312
427
 
313
428
  context :current_actor do
@@ -438,8 +553,6 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
438
553
  raise "already signaled" if @signaled
439
554
 
440
555
  @waiting = true
441
- signal :future
442
-
443
556
  value = wait :ponycopter
444
557
 
445
558
  @waiting = false
@@ -447,11 +560,6 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
447
560
  value
448
561
  end
449
562
 
450
- def wait_for_future
451
- return true if @waiting
452
- wait :future
453
- end
454
-
455
563
  def send_signal(value)
456
564
  signal :ponycopter, value
457
565
  end
@@ -467,9 +575,11 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
467
575
 
468
576
  obj.async.wait_for_signal
469
577
  obj.should_not be_signaled
578
+ obj.should be_waiting
470
579
 
471
580
  obj.send_signal :foobar
472
581
  obj.should be_signaled
582
+ obj.should_not be_waiting
473
583
  end
474
584
 
475
585
  it "sends values along with signals" do
@@ -478,7 +588,6 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
478
588
 
479
589
  future = obj.future(:wait_for_signal)
480
590
 
481
- obj.wait_for_future
482
591
  obj.should be_waiting
483
592
  obj.should_not be_signaled
484
593
 
@@ -799,12 +908,12 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
799
908
  end
800
909
  end
801
910
 
802
- context :mailbox_limit do
911
+ context :mailbox_size do
803
912
  subject do
804
913
  Class.new do
805
914
  include included_module
806
915
  task_class task_klass
807
- mailbox.max_size = 100
916
+ mailbox_size 100
808
917
  end
809
918
  end
810
919
 
@@ -857,4 +966,46 @@ shared_examples "Celluloid::Actor examples" do |included_module, task_klass|
857
966
  subclass.new.tasks.first.should be_a ExampleTask
858
967
  end
859
968
  end
969
+
970
+ context :timeouts do
971
+ let :actor_class do
972
+ Class.new do
973
+ include included_module
974
+
975
+ def name
976
+ sleep 0.5
977
+ :foo
978
+ end
979
+
980
+ def ask_name_with_timeout(other, duration)
981
+ timeout(duration) { other.name }
982
+ end
983
+ end
984
+ end
985
+
986
+ it "allows timing out tasks, raising Celluloid::Task::TimeoutError" do
987
+ a1 = actor_class.new
988
+ a2 = actor_class.new
989
+
990
+ expect { a1.ask_name_with_timeout a2, 0.3 }.to raise_error(Celluloid::Task::TimeoutError)
991
+ end
992
+
993
+ it "does not raise when it completes in time" do
994
+ a1 = actor_class.new
995
+ a2 = actor_class.new
996
+
997
+ a1.ask_name_with_timeout(a2, 0.6).should == :foo
998
+ end
999
+ end
1000
+
1001
+ context "raw message sends" do
1002
+ it "logs on unhandled messages" do
1003
+ Celluloid.logger = double.as_null_object
1004
+ Celluloid.logger.should_receive(:debug).with("Discarded message (unhandled): first")
1005
+
1006
+ actor = actor_class.new "Irma Gladden"
1007
+ actor.mailbox << :first
1008
+ sleep Celluloid::TIMER_QUANTUM
1009
+ end
1010
+ end
860
1011
  end