celluloid 0.11.1 → 0.12.0.pre

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.
@@ -0,0 +1,54 @@
1
+ module Celluloid
2
+ # High-priority internal system events
3
+ class SystemEvent; end
4
+
5
+ # Request to link with another actor
6
+ class LinkingRequest < SystemEvent
7
+ attr_reader :actor, :type
8
+
9
+ def initialize(actor, type)
10
+ @actor, @type = actor, type.to_sym
11
+ raise ArgumentError, "type must be link or unlink" unless [:link, :unlink].include?(@type)
12
+ end
13
+
14
+ def process(links)
15
+ case type
16
+ when :link then links << actor
17
+ when :unlink then links.delete actor
18
+ end
19
+
20
+ actor.mailbox << LinkingResponse.new(Actor.current, type)
21
+ end
22
+ end
23
+
24
+ # Response to a link request
25
+ class LinkingResponse
26
+ attr_reader :actor, :type
27
+
28
+ def initialize(actor, type)
29
+ @actor, @type = actor, type.to_sym
30
+ raise ArgumentError, "type must be link or unlink" unless [:link, :unlink].include?(@type)
31
+ end
32
+ end
33
+
34
+ # An actor has exited for the given reason
35
+ class ExitEvent < SystemEvent
36
+ attr_reader :actor, :reason
37
+
38
+ def initialize(actor, reason = nil)
39
+ @actor, @reason = actor, reason
40
+ end
41
+ end
42
+
43
+ # Name an actor at the time it's registered
44
+ class NamingRequest < SystemEvent
45
+ attr_reader :name
46
+
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+ end
51
+
52
+ # Request for an actor to terminate
53
+ class TerminationRequest < SystemEvent; end
54
+ end
@@ -1,78 +1,22 @@
1
+ require 'celluloid/tasks/task_fiber'
2
+ require 'celluloid/tasks/task_thread'
3
+
1
4
  module Celluloid
2
5
  # Trying to resume a dead task
3
6
  class DeadTaskError < StandardError; end
4
7
 
5
8
  # Tasks are interruptable/resumable execution contexts used to run methods
6
- class Task
7
- class TerminatedError < StandardError; end # kill a running fiber
8
-
9
- attr_reader :type
10
- attr_accessor :status
9
+ module Task
10
+ class TerminatedError < StandardError; end # kill a running task
11
11
 
12
12
  # Obtain the current task
13
13
  def self.current
14
- Fiber.current.task or raise "no task for this Fiber"
14
+ Thread.current[:task] or raise "not within a task context"
15
15
  end
16
16
 
17
17
  # Suspend the running task, deferring to the scheduler
18
18
  def self.suspend(status)
19
- task = Task.current
20
- task.status = status
21
-
22
- result = Fiber.yield
23
- raise TerminatedError, "task was terminated" if result == TerminatedError
24
- task.status = :running
25
-
26
- result
27
- end
28
-
29
- # Run the given block within a task
30
- def initialize(type)
31
- @type = type
32
- @status = :new
33
-
34
- actor = Thread.current[:actor]
35
- mailbox = Thread.current[:mailbox]
36
-
37
- @fiber = Fiber.new do
38
- @status = :running
39
- Thread.current[:actor] = actor
40
- Thread.current[:mailbox] = mailbox
41
- Fiber.current.task = self
42
- actor.tasks << self
43
-
44
- begin
45
- yield
46
- rescue TerminatedError
47
- # Task was explicitly terminated
48
- ensure
49
- @status = :dead
50
- actor.tasks.delete self
51
- end
52
- end
53
- end
54
-
55
- # Resume a suspended task, giving it a value to return if needed
56
- def resume(value = nil)
57
- @fiber.resume value
58
- nil
59
- rescue FiberError
60
- raise DeadTaskError, "cannot resume a dead task"
61
- end
62
-
63
- # Terminate this task
64
- def terminate
65
- resume TerminatedError if @fiber.alive?
66
- rescue FiberError
67
- # If we're getting this the task should already be dead
68
- end
69
-
70
- # Is the current task still running?
71
- def running?; @fiber.alive?; end
72
-
73
- # Nicer string inspect for tasks
74
- def inspect
75
- "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}, @running=#{@fiber.alive?}>"
19
+ Task.current.suspend(status)
76
20
  end
77
21
  end
78
22
  end
@@ -0,0 +1,65 @@
1
+ module Celluloid
2
+ # Tasks with a Fiber backend
3
+ class TaskFiber
4
+ attr_reader :type, :status
5
+
6
+ # Run the given block within a task
7
+ def initialize(type)
8
+ @type = type
9
+ @status = :new
10
+
11
+ actor, mailbox = Thread.current[:actor], Thread.current[:mailbox]
12
+ raise NotActorError, "can't create tasks outside of actors" unless actor
13
+
14
+ @fiber = Fiber.new do
15
+ @status = :running
16
+ Thread.current[:actor] = actor
17
+ Thread.current[:mailbox] = mailbox
18
+ Thread.current[:task] = self
19
+ actor.tasks << self
20
+
21
+ begin
22
+ yield
23
+ rescue Task::TerminatedError
24
+ # Task was explicitly terminated
25
+ ensure
26
+ @status = :dead
27
+ actor.tasks.delete self
28
+ end
29
+ end
30
+ end
31
+
32
+ # Suspend the current task, changing the status to the given argument
33
+ def suspend(status)
34
+ @status = status
35
+ result = Fiber.yield
36
+ raise result if result.is_a?(Task::TerminatedError)
37
+ @status = :running
38
+
39
+ result
40
+ end
41
+
42
+ # Resume a suspended task, giving it a value to return if needed
43
+ def resume(value = nil)
44
+ @fiber.resume value
45
+ nil
46
+ rescue FiberError
47
+ raise DeadTaskError, "cannot resume a dead task"
48
+ end
49
+
50
+ # Terminate this task
51
+ def terminate
52
+ resume Task::TerminatedError.new("task was terminated") if @fiber.alive?
53
+ rescue FiberError
54
+ # If we're getting this the task should already be dead
55
+ end
56
+
57
+ # Is the current task still running?
58
+ def running?; @fiber.alive?; end
59
+
60
+ # Nicer string inspect for tasks
61
+ def inspect
62
+ "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,70 @@
1
+ module Celluloid
2
+ # Tasks with a Thread backend
3
+ class TaskThread
4
+ attr_reader :type, :status
5
+
6
+ # Run the given block within a task
7
+ def initialize(type)
8
+ @type = type
9
+ @status = :new
10
+ @yield = Queue.new
11
+ @resume = Queue.new
12
+
13
+ actor, mailbox = Thread.current[:actor], Thread.current[:mailbox]
14
+ raise NotActorError, "can't create tasks outside of actors" unless actor
15
+
16
+ @thread = InternalPool.get do
17
+ @status = :running
18
+ Thread.current[:actor] = actor
19
+ Thread.current[:mailbox] = mailbox
20
+ Thread.current[:task] = self
21
+ actor.tasks << self
22
+
23
+ begin
24
+ @yield.push(yield(@resume.pop))
25
+ rescue Task::TerminatedError
26
+ # Task was explicitly terminated
27
+ ensure
28
+ @status = :dead
29
+ actor.tasks.delete self
30
+ @waiter.run if @waiter
31
+ end
32
+ end
33
+ end
34
+
35
+ # Suspend the current task, changing the status to the given argument
36
+ def suspend(status)
37
+ @status = status
38
+ @yield.push(nil)
39
+ result = @resume.pop
40
+
41
+ raise result if result.is_a?(Task::TerminatedError)
42
+ @status = :running
43
+
44
+ result
45
+ end
46
+
47
+ # Resume a suspended task, giving it a value to return if needed
48
+ def resume(value = nil)
49
+ raise DeadTaskError, "cannot resume a dead task" unless @thread.alive?
50
+ @resume.push(value)
51
+ @yield.pop
52
+ nil
53
+ rescue ThreadError
54
+ raise DeadTaskError, "cannot resume a dead task"
55
+ end
56
+
57
+ # Terminate this task
58
+ def terminate
59
+ resume Task::TerminatedError.new("task was terminated") if @thread.alive?
60
+ end
61
+
62
+ # Is the current task still running?
63
+ def running?; @status != :dead; end
64
+
65
+ # Nicer string inspect for tasks
66
+ def inspect
67
+ "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
68
+ end
69
+ end
70
+ end
@@ -27,12 +27,13 @@ module Celluloid
27
27
  # Forcibly kill the thread
28
28
  def kill
29
29
  !!@mutex.synchronize { @thread.kill if @thread }
30
+ self
30
31
  end
31
32
 
32
33
  # Join to a running thread, blocking until it terminates
33
34
  def join
34
35
  @mutex.synchronize { @join.wait(@mutex) if @thread }
35
- nil
36
+ self
36
37
  end
37
38
  end
38
39
  end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.11.1'
2
+ VERSION = '0.12.0.pre'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -19,11 +19,22 @@ shared_context "a Celluloid Actor" do |included_module|
19
19
  end.should be_true
20
20
  end
21
21
 
22
+ it "can be stored in hashes" do
23
+ actor = actor_class.new "Troy McClure"
24
+ actor.hash.should_not == Kernel.hash
25
+ actor.object_id.should_not == Kernel.object_id
26
+ end
27
+
22
28
  it "handles synchronous calls" do
23
29
  actor = actor_class.new "Troy McClure"
24
30
  actor.greet.should == "Hi, I'm Troy McClure"
25
31
  end
26
32
 
33
+ it "handles synchronous calls via #method" do
34
+ method = actor_class.new("Troy McClure").method(:greet)
35
+ method.call.should == "Hi, I'm Troy McClure"
36
+ end
37
+
27
38
  it "handles futures for synchronous calls" do
28
39
  actor = actor_class.new "Troy McClure"
29
40
  future = actor.future :greet
@@ -54,6 +65,12 @@ shared_context "a Celluloid Actor" do |included_module|
54
65
  actor.first.should be == :bar
55
66
  end
56
67
 
68
+ it "properly handles respond_to with include_private" do
69
+ actor = actor_class.new "Method missing privates"
70
+ actor.respond_to?(:zomg_private).should be_false
71
+ actor.respond_to?(:zomg_private, true).should be_true
72
+ end
73
+
57
74
  it "raises NoMethodError when a nonexistent method is called" do
58
75
  actor = actor_class.new "Billy Bob Thornton"
59
76
 
@@ -193,15 +210,15 @@ shared_context "a Celluloid Actor" do |included_module|
193
210
  actor = actor_class.new "Arnold Schwarzenegger"
194
211
  actor.should be_alive
195
212
  actor.terminate
196
- actor.join
213
+ Celluloid::Actor.join(actor)
197
214
  actor.should_not be_alive
198
215
  end
199
216
 
200
217
  it "kills" do
201
218
  actor = actor_class.new "Woody Harrelson"
202
219
  actor.should be_alive
203
- actor.kill
204
- actor.join
220
+ Celluloid::Actor.kill(actor)
221
+ Celluloid::Actor.join(actor)
205
222
  actor.should_not be_alive
206
223
  end
207
224
 
@@ -213,6 +230,15 @@ shared_context "a Celluloid Actor" do |included_module|
213
230
  actor.greet
214
231
  end.to raise_exception(Celluloid::DeadActorError)
215
232
  end
233
+
234
+ it "raises the right DeadActorError if terminate! called after terminated" do
235
+ actor = actor_class.new "Arnold Schwarzenegger"
236
+ actor.terminate
237
+
238
+ expect do
239
+ actor.terminate!
240
+ end.to raise_exception(Celluloid::DeadActorError, "actor already terminated")
241
+ end
216
242
  end
217
243
 
218
244
  context :current_actor do
@@ -236,16 +262,39 @@ shared_context "a Celluloid Actor" do |included_module|
236
262
 
237
263
  it "links to other actors" do
238
264
  @kevin.link @charlie
239
- @kevin.linked_to?(@charlie).should be_true
240
- @charlie.linked_to?(@kevin).should be_true
265
+ @kevin.monitoring?(@charlie).should be_true
266
+ @kevin.linked_to?(@charlie).should be_true
267
+ @charlie.monitoring?(@kevin).should be_true
268
+ @charlie.linked_to?(@kevin).should be_true
241
269
  end
242
270
 
243
271
  it "unlinks from other actors" do
244
272
  @kevin.link @charlie
245
273
  @kevin.unlink @charlie
246
274
 
247
- @kevin.linked_to?(@charlie).should be_false
248
- @charlie.linked_to?(@kevin).should be_false
275
+ @kevin.monitoring?(@charlie).should be_false
276
+ @kevin.linked_to?(@charlie).should be_false
277
+ @charlie.monitoring?(@kevin).should be_false
278
+ @charlie.linked_to?(@kevin).should be_false
279
+ end
280
+
281
+ it "monitors other actors unidirectionally" do
282
+ @kevin.monitor @charlie
283
+
284
+ @kevin.monitoring?(@charlie).should be_true
285
+ @kevin.linked_to?(@charlie).should be_false
286
+ @charlie.monitoring?(@kevin).should be_false
287
+ @charlie.linked_to?(@kevin).should be_false
288
+ end
289
+
290
+ it "unmonitors other actors" do
291
+ @kevin.monitor @charlie
292
+ @kevin.unmonitor @charlie
293
+
294
+ @kevin.monitoring?(@charlie).should be_false
295
+ @kevin.linked_to?(@charlie).should be_false
296
+ @charlie.monitoring?(@kevin).should be_false
297
+ @charlie.linked_to?(@kevin).should be_false
249
298
  end
250
299
 
251
300
  it "traps exit messages from other actors" do
@@ -357,6 +406,38 @@ shared_context "a Celluloid Actor" do |included_module|
357
406
  end
358
407
  end
359
408
 
409
+ context "exclusive classes" do
410
+ subject do
411
+ Class.new do
412
+ include included_module
413
+ exclusive
414
+
415
+ attr_reader :tasks
416
+
417
+ def initialize
418
+ @tasks = []
419
+ end
420
+
421
+ def eat_donuts
422
+ sleep Q
423
+ @tasks << 'donuts'
424
+ end
425
+
426
+ def drink_coffee
427
+ @tasks << 'coffee'
428
+ end
429
+ end
430
+ end
431
+
432
+ it "executes two methods in an exclusive order" do
433
+ actor = subject.new
434
+ actor.eat_donuts!
435
+ actor.drink_coffee!
436
+ sleep Q * 2
437
+ actor.tasks.should == ['donuts', 'coffee']
438
+ end
439
+ end
440
+
360
441
  context :receiving do
361
442
  before do
362
443
  @receiver = Class.new do
@@ -526,7 +607,7 @@ shared_context "a Celluloid Actor" do |included_module|
526
607
  tasks.size.should == 2
527
608
 
528
609
  blocking_task = tasks.find { |t| t.status != :running }
529
- blocking_task.should be_a Celluloid::Task
610
+ blocking_task.should be_a Celluloid.task_class
530
611
  blocking_task.status.should == :callwait
531
612
 
532
613
  actor.blocker.unblock
@@ -534,4 +615,39 @@ shared_context "a Celluloid Actor" do |included_module|
534
615
  actor.tasks.size.should == 1
535
616
  end
536
617
  end
618
+
619
+ context :use_mailbox do
620
+ class ExampleMailbox < Celluloid::Mailbox; end
621
+
622
+ subject do
623
+ Class.new do
624
+ include included_module
625
+ use_mailbox ExampleMailbox
626
+ end
627
+ end
628
+
629
+ it "uses user-specified mailboxes" do
630
+ subject.new.mailbox.should be_a ExampleMailbox
631
+ end
632
+
633
+ it "retains custom mailboxes when subclassed" do
634
+ subclass = Class.new(subject)
635
+ subclass.new.mailbox.should be_a ExampleMailbox
636
+ end
637
+ end
638
+
639
+ context :task_class do
640
+ class ExampleTask < Celluloid::TaskFiber; end
641
+
642
+ subject do
643
+ Class.new do
644
+ include included_module
645
+ task_class ExampleTask
646
+ end
647
+ end
648
+
649
+ it "uses user-specified tasks" do
650
+ subject.new.tasks.first.should be_a ExampleTask
651
+ end
652
+ end
537
653
  end