celluloid 0.11.1 → 0.12.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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