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.
- 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
|