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.
- data/README.md +23 -6
- data/lib/celluloid.rb +118 -109
- data/lib/celluloid/actor.rb +145 -55
- data/lib/celluloid/actor_proxy.rb +24 -21
- data/lib/celluloid/boot.rb +9 -0
- data/lib/celluloid/calls.rb +3 -3
- data/lib/celluloid/core_ext.rb +1 -6
- data/lib/celluloid/cpu_counter.rb +2 -0
- data/lib/celluloid/future.rb +3 -0
- data/lib/celluloid/links.rb +1 -3
- data/lib/celluloid/mailbox.rb +14 -20
- data/lib/celluloid/method.rb +19 -0
- data/lib/celluloid/notifications.rb +16 -17
- data/lib/celluloid/pool_manager.rb +40 -11
- data/lib/celluloid/registry.rb +8 -5
- data/lib/celluloid/rspec.rb +4 -0
- data/lib/celluloid/supervision_group.rb +23 -10
- data/lib/celluloid/system_events.rb +54 -0
- data/lib/celluloid/task.rb +7 -63
- data/lib/celluloid/tasks/task_fiber.rb +65 -0
- data/lib/celluloid/tasks/task_thread.rb +70 -0
- data/lib/celluloid/thread_handle.rb +2 -1
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +124 -8
- data/spec/support/example_actor_class.rb +1 -1
- data/spec/support/mailbox_examples.rb +3 -18
- data/spec/support/task_examples.rb +36 -0
- metadata +17 -12
- data/lib/celluloid/events.rb +0 -26
@@ -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
|
data/lib/celluloid/task.rb
CHANGED
@@ -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
|
-
|
7
|
-
class TerminatedError < StandardError; end # kill a running
|
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
|
-
|
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
|
-
|
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
|
-
|
36
|
+
self
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
data/lib/celluloid/version.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
204
|
-
|
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.
|
240
|
-
@
|
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.
|
248
|
-
@
|
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
|
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
|