celluloid 0.12.0 → 0.13.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 (40) hide show
  1. data/README.md +36 -23
  2. data/lib/celluloid/actor.rb +74 -47
  3. data/lib/celluloid/autostart.rb +16 -0
  4. data/lib/celluloid/calls.rb +52 -52
  5. data/lib/celluloid/condition.rb +70 -0
  6. data/lib/celluloid/core_ext.rb +1 -1
  7. data/lib/celluloid/cpu_counter.rb +7 -1
  8. data/lib/celluloid/fsm.rb +49 -16
  9. data/lib/celluloid/future.rb +5 -2
  10. data/lib/celluloid/internal_pool.rb +62 -44
  11. data/lib/celluloid/legacy.rb +46 -0
  12. data/lib/celluloid/links.rb +0 -5
  13. data/lib/celluloid/logger.rb +14 -4
  14. data/lib/celluloid/logging/incident.rb +21 -0
  15. data/lib/celluloid/logging/incident_logger.rb +129 -0
  16. data/lib/celluloid/logging/incident_reporter.rb +48 -0
  17. data/lib/celluloid/logging/log_event.rb +20 -0
  18. data/lib/celluloid/logging/ring_buffer.rb +65 -0
  19. data/lib/celluloid/logging.rb +5 -0
  20. data/lib/celluloid/mailbox.rb +2 -1
  21. data/lib/celluloid/method.rb +5 -0
  22. data/lib/celluloid/pool_manager.rb +21 -8
  23. data/lib/celluloid/proxies/actor_proxy.rb +11 -11
  24. data/lib/celluloid/proxies/async_proxy.rb +5 -1
  25. data/lib/celluloid/responses.rb +8 -6
  26. data/lib/celluloid/stack_dump.rb +94 -0
  27. data/lib/celluloid/supervision_group.rb +5 -3
  28. data/lib/celluloid/system_events.rb +11 -0
  29. data/lib/celluloid/tasks/task_fiber.rb +17 -9
  30. data/lib/celluloid/tasks/task_thread.rb +24 -14
  31. data/lib/celluloid/tasks.rb +59 -0
  32. data/lib/celluloid/thread_handle.rb +10 -1
  33. data/lib/celluloid/version.rb +1 -1
  34. data/lib/celluloid.rb +146 -87
  35. data/spec/support/actor_examples.rb +242 -114
  36. data/spec/support/example_actor_class.rb +17 -5
  37. data/spec/support/task_examples.rb +11 -2
  38. metadata +58 -14
  39. data/lib/celluloid/boot.rb +0 -9
  40. data/lib/celluloid/task.rb +0 -22
@@ -30,11 +30,27 @@ shared_context "a Celluloid Actor" do |included_module|
30
30
  actor.greet.should == "Hi, I'm Troy McClure"
31
31
  end
32
32
 
33
+ it "supports synchronous calls with blocks" do
34
+ actor = actor_class.new "Blocky Ralboa"
35
+
36
+ block_executed = false
37
+ actor.run { block_executed = true }
38
+ block_executed.should be_true
39
+ end
40
+
33
41
  it "supports synchronous calls via #method" do
34
42
  method = actor_class.new("Troy McClure").method(:greet)
35
43
  method.call.should == "Hi, I'm Troy McClure"
36
44
  end
37
45
 
46
+ it "supports #arity calls via #method" do
47
+ method = actor_class.new("Troy McClure").method(:greet)
48
+ method.arity.should == 0
49
+
50
+ method = actor_class.new("Troy McClure").method(:change_name)
51
+ method.arity.should == 1
52
+ end
53
+
38
54
  it "supports future(:method) syntax for synchronous future calls" do
39
55
  actor = actor_class.new "Troy McClure"
40
56
  future = actor.future :greet
@@ -77,75 +93,11 @@ shared_context "a Celluloid Actor" do |included_module|
77
93
  actor.respond_to?(:zomg_private, true).should be_true
78
94
  end
79
95
 
80
- it "raises NoMethodError when a nonexistent method is called" do
81
- actor = actor_class.new "Billy Bob Thornton"
82
-
83
- expect do
84
- actor.the_method_that_wasnt_there
85
- end.to raise_exception(NoMethodError)
86
- end
87
-
88
- it "reraises exceptions which occur during synchronous calls in the caller" do
89
- actor = actor_class.new "James Dean" # is this in bad taste?
90
-
91
- expect do
92
- actor.crash
93
- end.to raise_exception(ExampleCrash)
94
- end
95
-
96
- describe "when #abort is called" do
97
- it "raises exceptions in the caller but keeps running" do
98
- actor = actor_class.new "Al Pacino"
99
-
100
- e = nil
101
- line_no = nil
102
-
103
- expect do
104
- begin
105
- line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
106
- rescue => ex
107
- e = ex
108
- raise
109
- end
110
- end.to raise_exception(ExampleCrash, "You die motherfucker!")
111
-
112
- e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
113
- e.foo.should be == :bar # Check the exception maintains instance variables
114
-
115
- actor.should be_alive
116
- end
117
-
118
- it "converts strings to runtime errors" do
119
- actor = actor_class.new "Al Pacino"
120
- expect do
121
- actor.crash_with_abort_raw "foo"
122
- end.to raise_exception(RuntimeError, "foo")
123
- end
124
-
125
- it "crashes the caller if we pass neither String nor Exception" do
126
- actor = actor_class.new "Al Pacino"
127
- expect do
128
- actor.crash_with_abort_raw 10
129
- end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
130
-
131
- actor.greet rescue nil # Ensure our actor has died.
132
- actor.should_not be_alive
133
- end
134
- end
135
-
136
- it "raises DeadActorError if methods are synchronously called on a dead actor" do
137
- actor = actor_class.new "James Dean"
138
- actor.crash rescue nil
139
-
140
- expect do
141
- actor.greet
142
- end.to raise_exception(Celluloid::DeadActorError)
143
- end
144
-
145
- it "supports method! syntax for asynchronous calls" do
146
- actor = actor_class.new "Troy McClure"
147
- actor.change_name! "Charlie Sheen"
148
- actor.greet.should == "Hi, I'm Charlie Sheen"
96
+ it "calls the user defined finalizer" do
97
+ actor = actor_class.new "Mr. Bean"
98
+ actor.wrapped_object.should_receive(:my_finalizer)
99
+ actor.terminate
100
+ Celluloid::Actor.join(actor)
149
101
  end
150
102
 
151
103
  it "supports async(:method) syntax for asynchronous calls" do
@@ -160,19 +112,13 @@ shared_context "a Celluloid Actor" do |included_module|
160
112
  actor.greet.should == "Hi, I'm Charlie Sheen"
161
113
  end
162
114
 
163
- it "supports method! syntax for asynchronous calls to itself" do
164
- actor = actor_class.new "Troy McClure"
165
- actor.change_name_with_a_bang "Charlie Sheen"
166
- actor.greet.should == "Hi, I'm Charlie Sheen"
167
- end
168
-
169
115
  it "supports async.method syntax for asynchronous calls to itself" do
170
116
  actor = actor_class.new "Troy McClure"
171
117
  actor.change_name_async "Charlie Sheen"
172
118
  actor.greet.should == "Hi, I'm Charlie Sheen"
173
119
  end
174
120
 
175
- it "allows an actor to call private methods asynchronously with a bang" do
121
+ it "allows an actor to call private methods asynchronously" do
176
122
  actor = actor_class.new "Troy McClure"
177
123
  actor.call_private
178
124
  actor.private_called.should be_true
@@ -184,11 +130,12 @@ shared_context "a Celluloid Actor" do |included_module|
184
130
  actor.run do
185
131
  Celluloid.actor?
186
132
  end.should be_true
133
+ actor.should be_actor
187
134
  end
188
135
 
189
136
  it "inspects properly" do
190
137
  actor = actor_class.new "Troy McClure"
191
- actor.inspect.should match(/Celluloid::Actor\(/)
138
+ actor.inspect.should match(/Celluloid::ActorProxy\(/)
192
139
  actor.inspect.should match(/#{actor_class}/)
193
140
  actor.inspect.should include('@name="Troy McClure"')
194
141
  actor.inspect.should_not include("@celluloid")
@@ -207,22 +154,111 @@ shared_context "a Celluloid Actor" do |included_module|
207
154
  actor.wrapped_object.should be_a actor_class
208
155
  end
209
156
 
210
- describe 'mocking methods' do
157
+ it "warns about leaked wrapped objects via #inspect" do
158
+ actor = actor_class.new "Troy McClure"
159
+
160
+ actor.inspect.should_not include Celluloid::BARE_OBJECT_WARNING_MESSAGE
161
+ actor.inspect_thunk.should_not include Celluloid::BARE_OBJECT_WARNING_MESSAGE
162
+ actor.wrapped_object.inspect.should include Celluloid::BARE_OBJECT_WARNING_MESSAGE
163
+ end
164
+
165
+ it "can override #send" do
166
+ actor = actor_class.new "Troy McClure"
167
+ actor.send('foo').should == 'oof'
168
+ end
169
+
170
+ context "mocking methods" do
211
171
  let(:actor) { actor_class.new "Troy McClure" }
212
172
 
213
173
  before do
214
174
  actor.wrapped_object.should_receive(:external_hello).once.and_return "World"
215
175
  end
216
176
 
217
- it 'works externally via the proxy' do
177
+ it "works externally via the proxy" do
218
178
  actor.external_hello.should == "World"
219
179
  end
220
180
 
221
- it 'works internally when called on self' do
181
+ it "works internally when called on self" do
222
182
  actor.internal_hello.should == "World"
223
183
  end
224
184
  end
225
185
 
186
+ context :exceptions do
187
+ it "reraises exceptions which occur during synchronous calls in the caller" do
188
+ actor = actor_class.new "James Dean" # is this in bad taste?
189
+
190
+ expect do
191
+ actor.crash
192
+ end.to raise_exception(ExampleCrash)
193
+ end
194
+
195
+ it "includes both caller and receiver in exception traces" do
196
+ ExampleReceiver = Class.new do
197
+ include included_module
198
+
199
+ def receiver_method
200
+ raise ExampleCrash, "the spec purposely crashed me :("
201
+ end
202
+ end
203
+
204
+ ExampleCaller = Class.new do
205
+ include included_module
206
+
207
+ def caller_method
208
+ ExampleReceiver.new.receiver_method
209
+ end
210
+ end
211
+
212
+ ex = nil
213
+ begin
214
+ ExampleCaller.new.caller_method
215
+ rescue => ex
216
+ end
217
+
218
+ ex.should be_a ExampleCrash
219
+ ex.backtrace.grep(/`caller_method'/).should be_true
220
+ ex.backtrace.grep(/`receiver_method'/).should be_true
221
+ end
222
+
223
+ it "raises DeadActorError if methods are synchronously called on a dead actor" do
224
+ actor = actor_class.new "James Dean"
225
+ actor.crash rescue nil
226
+
227
+ expect do
228
+ actor.greet
229
+ end.to raise_exception(Celluloid::DeadActorError)
230
+ end
231
+ end
232
+
233
+ context :abort do
234
+ it "raises exceptions in the caller but keeps running" do
235
+ actor = actor_class.new "Al Pacino"
236
+
237
+ expect do
238
+ actor.crash_with_abort "You die motherfucker!", :bar
239
+ end.to raise_exception(ExampleCrash, "You die motherfucker!")
240
+
241
+ actor.should be_alive
242
+ end
243
+
244
+ it "converts strings to runtime errors" do
245
+ actor = actor_class.new "Al Pacino"
246
+ expect do
247
+ actor.crash_with_abort_raw "foo"
248
+ end.to raise_exception(RuntimeError, "foo")
249
+ end
250
+
251
+ it "crashes the caller if we pass neither String nor Exception" do
252
+ actor = actor_class.new "Al Pacino"
253
+ expect do
254
+ actor.crash_with_abort_raw 10
255
+ end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
256
+
257
+ Celluloid::Actor.join(actor)
258
+ actor.should_not be_alive
259
+ end
260
+ end
261
+
226
262
  context :termination do
227
263
  it "terminates" do
228
264
  actor = actor_class.new "Arnold Schwarzenegger"
@@ -232,7 +268,7 @@ shared_context "a Celluloid Actor" do |included_module|
232
268
  actor.should_not be_alive
233
269
  end
234
270
 
235
- it "kills" do
271
+ it "kills" do # THOU SHALT ALWAYS KILL
236
272
  actor = actor_class.new "Woody Harrelson"
237
273
  actor.should be_alive
238
274
  Celluloid::Actor.kill(actor)
@@ -262,7 +298,7 @@ shared_context "a Celluloid Actor" do |included_module|
262
298
  context :current_actor do
263
299
  it "knows the current actor" do
264
300
  actor = actor_class.new "Roger Daltrey"
265
- actor.current_actor.should == actor
301
+ actor.current_actor.should eq actor
266
302
  end
267
303
 
268
304
  it "raises NotActorError if called outside an actor" do
@@ -278,6 +314,24 @@ shared_context "a Celluloid Actor" do |included_module|
278
314
  @charlie = actor_class.new "Charlie Sheen"
279
315
  end
280
316
 
317
+ let(:supervisor_class) do
318
+ Class.new do # like a boss
319
+ include included_module
320
+ trap_exit :lambaste_subordinate
321
+
322
+ def initialize(name)
323
+ @name = name
324
+ @subordinate_lambasted = false
325
+ end
326
+
327
+ def subordinate_lambasted?; @subordinate_lambasted; end
328
+
329
+ def lambaste_subordinate(actor, reason)
330
+ @subordinate_lambasted = true
331
+ end
332
+ end
333
+ end
334
+
281
335
  it "links to other actors" do
282
336
  @kevin.link @charlie
283
337
  @kevin.monitoring?(@charlie).should be_true
@@ -316,23 +370,20 @@ shared_context "a Celluloid Actor" do |included_module|
316
370
  end
317
371
 
318
372
  it "traps exit messages from other actors" do
319
- boss = Class.new do # like a boss
320
- include included_module
321
- trap_exit :lambaste_subordinate
322
-
323
- def initialize(name)
324
- @name = name
325
- @subordinate_lambasted = false
326
- end
373
+ chuck = supervisor_class.new "Chuck Lorre"
374
+ chuck.link @charlie
327
375
 
328
- def subordinate_lambasted?; @subordinate_lambasted; end
376
+ expect do
377
+ @charlie.crash
378
+ end.to raise_exception(ExampleCrash)
329
379
 
330
- def lambaste_subordinate(actor, reason)
331
- @subordinate_lambasted = true
332
- end
333
- end
380
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
381
+ chuck.should be_subordinate_lambasted
382
+ end
334
383
 
335
- chuck = boss.new "Chuck Lorre"
384
+ it "traps exit messages from other actors in subclasses" do
385
+ supervisor_subclass = Class.new(supervisor_class)
386
+ chuck = supervisor_subclass.new "Chuck Lorre"
336
387
  chuck.link @charlie
337
388
 
338
389
  expect do
@@ -342,6 +393,18 @@ shared_context "a Celluloid Actor" do |included_module|
342
393
  sleep 0.1 # hax to prevent a race between exit handling and the next call
343
394
  chuck.should be_subordinate_lambasted
344
395
  end
396
+
397
+ it "unlinks from a dead linked actor" do
398
+ chuck = supervisor_class.new "Chuck Lorre"
399
+ chuck.link @charlie
400
+
401
+ expect do
402
+ @charlie.crash
403
+ end.to raise_exception(ExampleCrash)
404
+
405
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
406
+ chuck.links.count.should == 0
407
+ end
345
408
  end
346
409
 
347
410
  context :signaling do
@@ -385,7 +448,7 @@ shared_context "a Celluloid Actor" do |included_module|
385
448
  obj = @signaler.new
386
449
  obj.should_not be_signaled
387
450
 
388
- obj.wait_for_signal!
451
+ obj.async.wait_for_signal
389
452
  obj.should_not be_signaled
390
453
 
391
454
  obj.send_signal :foobar
@@ -411,15 +474,65 @@ shared_context "a Celluloid Actor" do |included_module|
411
474
  subject do
412
475
  Class.new do
413
476
  include included_module
414
- def exclusive_example
415
- exclusive?
477
+
478
+ attr_reader :tasks
479
+
480
+ def initialize
481
+ @tasks = []
482
+ end
483
+
484
+ def log_task(task)
485
+ @tasks << task
486
+ end
487
+
488
+ def exclusive_with_block_log_task(task)
489
+ exclusive do
490
+ sleep Celluloid::TIMER_QUANTUM
491
+ log_task(task)
492
+ end
493
+ end
494
+
495
+ def exclusive_log_task(task)
496
+ sleep Celluloid::TIMER_QUANTUM
497
+ log_task(task)
498
+ end
499
+ exclusive :exclusive_log_task
500
+
501
+ def check_not_exclusive
502
+ Celluloid.exclusive?
503
+ end
504
+
505
+ def check_exclusive
506
+ exclusive { Celluloid.exclusive? }
507
+ end
508
+
509
+ def nested_exclusive_example
510
+ exclusive { exclusive { nil }; Celluloid.exclusive? }
416
511
  end
417
- exclusive :exclusive_example
418
512
  end.new
419
513
  end
420
514
 
421
- it "supports exclusive methods" do
422
- subject.exclusive_example.should be_true
515
+ it "executes methods in the proper order with block form" do
516
+ subject.async.exclusive_with_block_log_task(:one)
517
+ subject.async.log_task(:two)
518
+ sleep Celluloid::TIMER_QUANTUM * 2
519
+ subject.tasks.should == [:one, :two]
520
+ end
521
+
522
+ it "executes methods in the proper order with a class-level annotation" do
523
+ subject.async.exclusive_log_task :one
524
+ subject.async.log_task :two
525
+ sleep Celluloid::TIMER_QUANTUM * 2
526
+ subject.tasks.should == [:one, :two]
527
+ end
528
+
529
+ it "knows when it's in exclusive mode" do
530
+ subject.check_not_exclusive.should be_false
531
+ subject.check_exclusive.should be_true
532
+ end
533
+
534
+ it "remains in exclusive mode inside nested blocks" do
535
+ subject.nested_exclusive_example.should be_true
423
536
  end
424
537
  end
425
538
 
@@ -448,8 +561,8 @@ shared_context "a Celluloid Actor" do |included_module|
448
561
 
449
562
  it "executes two methods in an exclusive order" do
450
563
  actor = subject.new
451
- actor.eat_donuts!
452
- actor.drink_coffee!
564
+ actor.async.eat_donuts
565
+ actor.async.drink_coffee
453
566
  sleep Celluloid::TIMER_QUANTUM * 2
454
567
  actor.tasks.should == ['donuts', 'coffee']
455
568
  end
@@ -489,7 +602,6 @@ shared_context "a Celluloid Actor" do |included_module|
489
602
  end
490
603
 
491
604
  context :timers do
492
-
493
605
  before do
494
606
  @klass = Class.new do
495
607
  include included_module
@@ -579,6 +691,22 @@ shared_context "a Celluloid Actor" do |included_module|
579
691
  sleep(interval + Celluloid::TIMER_QUANTUM) # wonky! #/
580
692
  actor.should_not be_fired
581
693
  end
694
+
695
+ it "allows delays from outside the actor" do
696
+ actor = @klass.new
697
+
698
+ interval = Celluloid::TIMER_QUANTUM * 10
699
+ started_at = Time.now
700
+ fired = false
701
+
702
+ timer = actor.after(interval) do
703
+ fired = true
704
+ end
705
+ fired.should be_false
706
+
707
+ sleep(interval + Celluloid::TIMER_QUANTUM) # wonky! #/
708
+ fired.should be_true
709
+ end
582
710
  end
583
711
 
584
712
  context :tasks do
@@ -631,13 +759,13 @@ shared_context "a Celluloid Actor" do |included_module|
631
759
  end
632
760
  end
633
761
 
634
- context :use_mailbox do
762
+ context :mailbox_class do
635
763
  class ExampleMailbox < Celluloid::Mailbox; end
636
764
 
637
765
  subject do
638
766
  Class.new do
639
767
  include included_module
640
- use_mailbox ExampleMailbox
768
+ mailbox_class ExampleMailbox
641
769
  end
642
770
  end
643
771
 
@@ -651,23 +779,23 @@ shared_context "a Celluloid Actor" do |included_module|
651
779
  end
652
780
  end
653
781
 
654
- context :mailbox_class do
655
- class ExampleMailbox < Celluloid::Mailbox; end
782
+ context :proxy_class do
783
+ class ExampleProxy < Celluloid::ActorProxy; end
656
784
 
657
785
  subject do
658
786
  Class.new do
659
787
  include included_module
660
- mailbox_class ExampleMailbox
788
+ proxy_class ExampleProxy
661
789
  end
662
790
  end
663
791
 
664
- it "overrides the mailbox class" do
665
- subject.new.mailbox.should be_a ExampleMailbox
792
+ it "uses user-specified proxy" do
793
+ subject.new.__class__.should == ExampleProxy
666
794
  end
667
795
 
668
- it "retains custom mailboxes when subclassed" do
796
+ it "retains custom proxy when subclassed" do
669
797
  subclass = Class.new(subject)
670
- subclass.new.mailbox.should be_a ExampleMailbox
798
+ subclass.new.__class__.should == ExampleProxy
671
799
  end
672
800
  end
673
801
 
@@ -3,6 +3,7 @@ module ExampleActorClass
3
3
  Class.new do
4
4
  include included_module
5
5
  attr_reader :name
6
+ finalizer :my_finalizer
6
7
 
7
8
  def initialize(name)
8
9
  @name = name
@@ -13,10 +14,6 @@ module ExampleActorClass
13
14
  @name = new_name
14
15
  end
15
16
 
16
- def change_name_with_a_bang(new_name)
17
- change_name! new_name
18
- end
19
-
20
17
  def change_name_async(new_name)
21
18
  async.change_name new_name
22
19
  end
@@ -25,6 +22,10 @@ module ExampleActorClass
25
22
  "Hi, I'm #{@name}"
26
23
  end
27
24
 
25
+ def actor?
26
+ Celluloid.actor?
27
+ end
28
+
28
29
  def run(*args)
29
30
  yield(*args)
30
31
  end
@@ -51,6 +52,14 @@ module ExampleActorClass
51
52
  "Hello"
52
53
  end
53
54
 
55
+ def inspect_thunk
56
+ inspect
57
+ end
58
+
59
+ def send(string)
60
+ string.reverse
61
+ end
62
+
54
63
  def method_missing(method_name, *args, &block)
55
64
  if delegates?(method_name)
56
65
  @delegate.send method_name, *args, &block
@@ -64,7 +73,7 @@ module ExampleActorClass
64
73
  end
65
74
 
66
75
  def call_private
67
- zomg_private!
76
+ async.zomg_private
68
77
  end
69
78
 
70
79
  def zomg_private
@@ -73,6 +82,9 @@ module ExampleActorClass
73
82
  private :zomg_private
74
83
  attr_reader :private_called
75
84
 
85
+ def my_finalizer
86
+ end
87
+
76
88
  private
77
89
 
78
90
  def delegates?(method_name)
@@ -14,11 +14,11 @@ shared_context "a Celluloid Task" do |task_class|
14
14
  subject { task_class.new(task_type) { Celluloid::Task.suspend(suspend_state) } }
15
15
 
16
16
  before :each do
17
- Thread.current[:actor] = actor
17
+ Thread.current[:celluloid_actor] = actor
18
18
  end
19
19
 
20
20
  after :each do
21
- Thread.current[:actor] = nil
21
+ Thread.current[:celluloid_actor] = nil
22
22
  end
23
23
 
24
24
  it "begins with status :new" do
@@ -33,4 +33,13 @@ shared_context "a Celluloid Task" do |task_class|
33
33
  subject.should_not be_running
34
34
  end
35
35
 
36
+ it "raises exceptions outside" do
37
+ task = task_class.new(task_type) do
38
+ raise "failure"
39
+ end
40
+ expect do
41
+ task.resume
42
+ end.to raise_exception("failure")
43
+ end
44
+
36
45
  end