celluloid 0.14.1 → 0.15.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.
- checksums.yaml +4 -4
- data/README.md +6 -2
- data/lib/celluloid.rb +92 -108
- data/lib/celluloid/actor.rb +42 -64
- data/lib/celluloid/autostart.rb +1 -1
- data/lib/celluloid/call_chain.rb +13 -0
- data/lib/celluloid/calls.rb +5 -8
- data/lib/celluloid/condition.rb +8 -10
- data/lib/celluloid/cpu_counter.rb +1 -1
- data/lib/celluloid/evented_mailbox.rb +7 -10
- data/lib/celluloid/fsm.rb +1 -1
- data/lib/celluloid/future.rb +1 -2
- data/lib/celluloid/internal_pool.rb +77 -20
- data/lib/celluloid/legacy.rb +0 -38
- data/lib/celluloid/mailbox.rb +17 -10
- data/lib/celluloid/pool_manager.rb +1 -1
- data/lib/celluloid/properties.rb +24 -0
- data/lib/celluloid/proxies/abstract_proxy.rb +3 -0
- data/lib/celluloid/proxies/actor_proxy.rb +3 -32
- data/lib/celluloid/proxies/async_proxy.rb +4 -8
- data/lib/celluloid/proxies/future_proxy.rb +8 -6
- data/lib/celluloid/proxies/sync_proxy.rb +12 -7
- data/lib/celluloid/rspec.rb +3 -1
- data/lib/celluloid/signals.rb +7 -35
- data/lib/celluloid/stack_dump.rb +50 -37
- data/lib/celluloid/supervision_group.rb +5 -5
- data/lib/celluloid/task_set.rb +49 -0
- data/lib/celluloid/tasks.rb +67 -42
- data/lib/celluloid/tasks/task_fiber.rb +3 -1
- data/lib/celluloid/tasks/task_thread.rb +2 -3
- data/lib/celluloid/thread.rb +2 -0
- data/spec/celluloid/actor_spec.rb +5 -0
- data/spec/celluloid/block_spec.rb +54 -0
- data/spec/celluloid/calls_spec.rb +42 -0
- data/spec/celluloid/condition_spec.rb +65 -0
- data/spec/celluloid/evented_mailbox_spec.rb +34 -0
- data/spec/celluloid/fsm_spec.rb +107 -0
- data/spec/celluloid/future_spec.rb +32 -0
- data/spec/celluloid/internal_pool_spec.rb +52 -0
- data/spec/celluloid/links_spec.rb +45 -0
- data/spec/celluloid/logging/ring_buffer_spec.rb +38 -0
- data/spec/celluloid/mailbox_spec.rb +5 -0
- data/spec/celluloid/notifications_spec.rb +120 -0
- data/spec/celluloid/pool_spec.rb +52 -0
- data/spec/celluloid/properties_spec.rb +42 -0
- data/spec/celluloid/registry_spec.rb +64 -0
- data/spec/celluloid/stack_dump_spec.rb +35 -0
- data/spec/celluloid/supervision_group_spec.rb +53 -0
- data/spec/celluloid/supervisor_spec.rb +92 -0
- data/spec/celluloid/tasks/task_fiber_spec.rb +5 -0
- data/spec/celluloid/tasks/task_thread_spec.rb +5 -0
- data/spec/celluloid/thread_handle_spec.rb +22 -0
- data/spec/celluloid/uuid_spec.rb +11 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/actor_examples.rb +161 -10
- data/spec/support/example_actor_class.rb +8 -0
- data/spec/support/mailbox_examples.rb +15 -3
- data/spec/support/task_examples.rb +2 -2
- metadata +28 -3
- data/lib/celluloid/version.rb +0 -4
data/lib/celluloid/tasks.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Asked to do task-related things outside a task
|
3
|
-
class NotTaskError <
|
3
|
+
class NotTaskError < Celluloid::Error; end
|
4
4
|
|
5
5
|
# Trying to resume a dead task
|
6
|
-
class DeadTaskError <
|
6
|
+
class DeadTaskError < Celluloid::Error; end
|
7
|
+
|
8
|
+
# Errors which should be resumed automatically
|
9
|
+
class ResumableError < Celluloid::Error; end
|
7
10
|
|
8
11
|
# Tasks are interruptable/resumable execution contexts used to run methods
|
9
12
|
class Task
|
10
|
-
class TerminatedError <
|
13
|
+
class TerminatedError < ResumableError; end # kill a running task after terminate
|
14
|
+
|
15
|
+
class TimeoutError < ResumableError; end # kill a running task after timeout
|
11
16
|
|
12
17
|
# Obtain the current task
|
13
18
|
def self.current
|
@@ -19,15 +24,20 @@ module Celluloid
|
|
19
24
|
Task.current.suspend(status)
|
20
25
|
end
|
21
26
|
|
22
|
-
attr_reader :type, :status
|
27
|
+
attr_reader :type, :meta, :status
|
28
|
+
attr_accessor :chain_id
|
23
29
|
|
24
30
|
# Create a new task
|
25
|
-
def initialize(type)
|
26
|
-
@type
|
27
|
-
@
|
31
|
+
def initialize(type, meta)
|
32
|
+
@type = type
|
33
|
+
@meta = meta
|
34
|
+
@status = :new
|
28
35
|
|
29
|
-
|
30
|
-
|
36
|
+
@exclusive = false
|
37
|
+
@dangerous_suspend = @meta ? @meta.delete(:dangerous_suspend) : false
|
38
|
+
|
39
|
+
actor = Thread.current[:celluloid_actor]
|
40
|
+
@chain_id = CallChain.current_id
|
31
41
|
|
32
42
|
raise NotActorError, "can't create tasks outside of actors" unless actor
|
33
43
|
|
@@ -35,8 +45,9 @@ module Celluloid
|
|
35
45
|
begin
|
36
46
|
@status = :running
|
37
47
|
actor.setup_thread
|
38
|
-
|
39
|
-
Thread.current[:
|
48
|
+
|
49
|
+
Thread.current[:celluloid_task] = self
|
50
|
+
CallChain.current_id = @chain_id
|
40
51
|
|
41
52
|
actor.tasks << self
|
42
53
|
yield
|
@@ -55,11 +66,25 @@ module Celluloid
|
|
55
66
|
|
56
67
|
# Suspend the current task, changing the status to the given argument
|
57
68
|
def suspend(status)
|
69
|
+
raise "Cannot suspend while in exclusive mode" if exclusive?
|
70
|
+
|
58
71
|
@status = status
|
72
|
+
|
73
|
+
if $CELLULOID_DEBUG && @dangerous_suspend
|
74
|
+
warning = "Dangerously suspending task: "
|
75
|
+
warning << [
|
76
|
+
"type=#{@type.inspect}",
|
77
|
+
"meta=#{@meta.inspect}",
|
78
|
+
"status=#{@status.inspect}"
|
79
|
+
].join(", ")
|
80
|
+
|
81
|
+
Logger.warn [warning, *caller[2..8]].join("\n\t")
|
82
|
+
end
|
83
|
+
|
59
84
|
value = signal
|
60
85
|
|
61
|
-
raise value if value.is_a?(Task::TerminatedError)
|
62
86
|
@status = :running
|
87
|
+
raise value if value.is_a?(Celluloid::ResumableError)
|
63
88
|
|
64
89
|
value
|
65
90
|
end
|
@@ -70,9 +95,37 @@ module Celluloid
|
|
70
95
|
nil
|
71
96
|
end
|
72
97
|
|
98
|
+
# Execute a code block in exclusive mode.
|
99
|
+
def exclusive
|
100
|
+
if @exclusive
|
101
|
+
yield
|
102
|
+
else
|
103
|
+
begin
|
104
|
+
@exclusive = true
|
105
|
+
yield
|
106
|
+
ensure
|
107
|
+
@exclusive = false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
73
112
|
# Terminate this task
|
74
113
|
def terminate
|
75
|
-
|
114
|
+
raise "Cannot terminate an exclusive task" if exclusive?
|
115
|
+
|
116
|
+
if running?
|
117
|
+
Celluloid.logger.warn "Terminating task: type=#{@type.inspect}, meta=#{@meta.inspect}, status=#{@status.inspect}"
|
118
|
+
exception = Task::TerminatedError.new("task was terminated")
|
119
|
+
exception.set_backtrace(caller)
|
120
|
+
resume exception
|
121
|
+
else
|
122
|
+
raise DeadTaskError, "task is already dead"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Is this task running in exclusive mode?
|
127
|
+
def exclusive?
|
128
|
+
@exclusive
|
76
129
|
end
|
77
130
|
|
78
131
|
def backtrace
|
@@ -83,35 +136,7 @@ module Celluloid
|
|
83
136
|
|
84
137
|
# Nicer string inspect for tasks
|
85
138
|
def inspect
|
86
|
-
"#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class TaskSet
|
91
|
-
include Enumerable
|
92
|
-
|
93
|
-
def initialize
|
94
|
-
@tasks = Set.new
|
95
|
-
end
|
96
|
-
|
97
|
-
def <<(task)
|
98
|
-
@tasks += [task]
|
99
|
-
end
|
100
|
-
|
101
|
-
def delete(task)
|
102
|
-
@tasks -= [task]
|
103
|
-
end
|
104
|
-
|
105
|
-
def each(&blk)
|
106
|
-
@tasks.each(&blk)
|
107
|
-
end
|
108
|
-
|
109
|
-
def first
|
110
|
-
@tasks.first
|
111
|
-
end
|
112
|
-
|
113
|
-
def empty?
|
114
|
-
@tasks.empty?
|
139
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @meta=#{@meta.inspect}, @status=#{@status.inspect}>"
|
115
140
|
end
|
116
141
|
end
|
117
142
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module Celluloid
|
2
|
-
class FiberStackError <
|
2
|
+
class FiberStackError < Celluloid::Error; end
|
3
3
|
|
4
4
|
# Tasks with a Fiber backend
|
5
5
|
class TaskFiber < Task
|
6
6
|
|
7
7
|
def create
|
8
|
+
queue = Thread.current[:celluloid_queue]
|
8
9
|
@fiber = Fiber.new do
|
9
10
|
# FIXME: cannot use the writer as specs run inside normal Threads
|
10
11
|
Thread.current[:celluloid_role] = :actor
|
12
|
+
Thread.current[:celluloid_queue] = queue
|
11
13
|
yield
|
12
14
|
end
|
13
15
|
end
|
@@ -2,7 +2,7 @@ module Celluloid
|
|
2
2
|
# Tasks with a Thread backend
|
3
3
|
class TaskThread < Task
|
4
4
|
# Run the given block within a task
|
5
|
-
def initialize(type)
|
5
|
+
def initialize(type, meta)
|
6
6
|
@resume_queue = Queue.new
|
7
7
|
@exception_queue = Queue.new
|
8
8
|
@yield_mutex = Mutex.new
|
@@ -12,8 +12,7 @@ module Celluloid
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def create
|
15
|
-
@thread = Celluloid.
|
16
|
-
Thread.current.role = :task
|
15
|
+
@thread = Celluloid::ThreadHandle.new(:task) do
|
17
16
|
begin
|
18
17
|
ex = @resume_queue.pop
|
19
18
|
raise ex if ex.is_a?(Task::TerminatedError)
|
data/lib/celluloid/thread.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Blocks" do
|
4
|
+
class MyBlockActor
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def ask_for_something(other)
|
13
|
+
sender_actor = current_actor
|
14
|
+
$data << [:outside, @name, current_actor.name]
|
15
|
+
other.do_something_and_callback do |value|
|
16
|
+
$data << [:yielded, @name, current_actor.name]
|
17
|
+
$data << self.receive_result(:self)
|
18
|
+
$data << current_actor.receive_result(:current_actor)
|
19
|
+
$data << sender_actor.receive_result(:sender)
|
20
|
+
"somevalue"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_something_and_callback
|
25
|
+
$data << [:something, @name, current_actor.name]
|
26
|
+
$data << yield(:foo)
|
27
|
+
end
|
28
|
+
|
29
|
+
def receive_result(result)
|
30
|
+
[result, @name, current_actor.name]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "works" do
|
35
|
+
$data = []
|
36
|
+
|
37
|
+
a1 = MyBlockActor.new("one")
|
38
|
+
a2 = MyBlockActor.new("two")
|
39
|
+
|
40
|
+
a1.ask_for_something a2
|
41
|
+
|
42
|
+
expected = [
|
43
|
+
[:outside, "one", "one"],
|
44
|
+
[:something, "two", "two"],
|
45
|
+
[:yielded, "one", "one"],
|
46
|
+
[:self, "one", "one"],
|
47
|
+
[:current_actor, "one", "one"],
|
48
|
+
[:sender, "one", "one"],
|
49
|
+
"somevalue",
|
50
|
+
]
|
51
|
+
|
52
|
+
$data.should eq(expected)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Celluloid::SyncCall do
|
4
|
+
class CallExampleActor
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
def initialize(next_actor = nil)
|
8
|
+
@next = next_actor
|
9
|
+
end
|
10
|
+
|
11
|
+
def actual_method; end
|
12
|
+
|
13
|
+
def chained_call_ids
|
14
|
+
[call_chain_id, @next.call_chain_id]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:actor) { CallExampleActor.new }
|
19
|
+
|
20
|
+
it "aborts with NoMethodError when a nonexistent method is called" do
|
21
|
+
expect do
|
22
|
+
actor.the_method_that_wasnt_there
|
23
|
+
end.to raise_exception(NoMethodError)
|
24
|
+
|
25
|
+
actor.should be_alive
|
26
|
+
end
|
27
|
+
|
28
|
+
it "aborts with ArgumentError when a method is called with too many arguments" do
|
29
|
+
expect do
|
30
|
+
actor.actual_method("with too many arguments")
|
31
|
+
end.to raise_exception(ArgumentError)
|
32
|
+
|
33
|
+
actor.should be_alive
|
34
|
+
end
|
35
|
+
|
36
|
+
it "preserves call chains across synchronous calls" do
|
37
|
+
actor2 = CallExampleActor.new(actor)
|
38
|
+
|
39
|
+
uuid, next_actor_uuid = actor2.chained_call_ids
|
40
|
+
uuid.should eq next_actor_uuid
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Celluloid::Condition do
|
4
|
+
class ConditionExample
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
attr_reader :condition, :signaled_times
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@condition = Condition.new
|
11
|
+
|
12
|
+
@waiting = false
|
13
|
+
@signaled_times = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def signal_condition(condition, value)
|
17
|
+
condition.signal value
|
18
|
+
end
|
19
|
+
|
20
|
+
def wait_for_condition
|
21
|
+
@waiting = true
|
22
|
+
begin
|
23
|
+
value = @condition.wait
|
24
|
+
@signaled_times += 1
|
25
|
+
ensure
|
26
|
+
@waiting = false
|
27
|
+
end
|
28
|
+
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def waiting?; @waiting end
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:actor) { ConditionExample.new }
|
36
|
+
after { actor.terminate rescue nil }
|
37
|
+
|
38
|
+
it "sends signals" do
|
39
|
+
3.times { actor.async.wait_for_condition }
|
40
|
+
actor.signaled_times.should be_zero
|
41
|
+
|
42
|
+
actor.condition.signal
|
43
|
+
actor.signaled_times.should be(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "broadcasts signals" do
|
47
|
+
3.times { actor.async.wait_for_condition }
|
48
|
+
actor.signaled_times.should be_zero
|
49
|
+
|
50
|
+
actor.condition.broadcast
|
51
|
+
actor.signaled_times.should be(3)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "sends values along with signals" do
|
55
|
+
future = actor.future(:wait_for_condition)
|
56
|
+
actor.condition.signal(:example_value)
|
57
|
+
future.value.should be(:example_value)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "supports waiting outside actors" do
|
61
|
+
condition = Celluloid::Condition.new
|
62
|
+
actor.async.signal_condition condition, :value
|
63
|
+
condition.wait.should eq(:value)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestEventedMailbox < Celluloid::EventedMailbox
|
4
|
+
class Reactor
|
5
|
+
def initialize
|
6
|
+
@condition = ConditionVariable.new
|
7
|
+
@mutex = Mutex.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def wakeup
|
11
|
+
@mutex.synchronize do
|
12
|
+
@condition.signal
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_once(timeout)
|
17
|
+
@mutex.synchronize do
|
18
|
+
@condition.wait(@mutex, timeout)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def shutdown
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
super(Reactor)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Celluloid::EventedMailbox do
|
32
|
+
subject { TestEventedMailbox.new }
|
33
|
+
it_behaves_like "a Celluloid Mailbox"
|
34
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Celluloid::FSM do
|
4
|
+
before :all do
|
5
|
+
class TestMachine
|
6
|
+
include Celluloid::FSM
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
@fired = false
|
11
|
+
end
|
12
|
+
|
13
|
+
state :callbacked do
|
14
|
+
@fired = true
|
15
|
+
end
|
16
|
+
|
17
|
+
state :pre_done, :to => :done
|
18
|
+
state :another, :done
|
19
|
+
|
20
|
+
def fired?; @fired end
|
21
|
+
end
|
22
|
+
|
23
|
+
class DummyActor
|
24
|
+
include Celluloid
|
25
|
+
end
|
26
|
+
|
27
|
+
class CustomDefaultMachine
|
28
|
+
include Celluloid::FSM
|
29
|
+
|
30
|
+
default_state :foobar
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
subject { TestMachine.new }
|
35
|
+
|
36
|
+
it "starts in the default state" do
|
37
|
+
subject.state.should eq(TestMachine.default_state)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "transitions between states" do
|
41
|
+
subject.state.should_not be :done
|
42
|
+
subject.transition :done
|
43
|
+
subject.state.should be :done
|
44
|
+
end
|
45
|
+
|
46
|
+
it "fires callbacks for states" do
|
47
|
+
subject.should_not be_fired
|
48
|
+
subject.transition :callbacked
|
49
|
+
subject.should be_fired
|
50
|
+
end
|
51
|
+
|
52
|
+
it "allows custom default states" do
|
53
|
+
CustomDefaultMachine.new.state.should be :foobar
|
54
|
+
end
|
55
|
+
|
56
|
+
it "supports constraints on valid state transitions" do
|
57
|
+
subject.transition :pre_done
|
58
|
+
expect { subject.transition :another }.to raise_exception ArgumentError
|
59
|
+
end
|
60
|
+
|
61
|
+
it "transitions to states after a specified delay" do
|
62
|
+
interval = Celluloid::TIMER_QUANTUM * 10
|
63
|
+
|
64
|
+
subject.attach DummyActor.new
|
65
|
+
subject.transition :another
|
66
|
+
subject.transition :done, :delay => interval
|
67
|
+
|
68
|
+
subject.state.should be :another
|
69
|
+
sleep interval + Celluloid::TIMER_QUANTUM
|
70
|
+
|
71
|
+
subject.state.should be :done
|
72
|
+
end
|
73
|
+
|
74
|
+
it "cancels delayed state transitions if another transition is made" do
|
75
|
+
interval = Celluloid::TIMER_QUANTUM * 10
|
76
|
+
|
77
|
+
subject.attach DummyActor.new
|
78
|
+
subject.transition :another
|
79
|
+
subject.transition :done, :delay => interval
|
80
|
+
|
81
|
+
subject.state.should be :another
|
82
|
+
subject.transition :pre_done
|
83
|
+
sleep interval + Celluloid::TIMER_QUANTUM
|
84
|
+
|
85
|
+
subject.state.should be :pre_done
|
86
|
+
end
|
87
|
+
|
88
|
+
context "actor is not set" do
|
89
|
+
context "transition is delayed" do
|
90
|
+
it "raises an unattached error" do
|
91
|
+
expect { subject.transition :another, :delay => 100 } \
|
92
|
+
.to raise_error(Celluloid::FSM::UnattachedError)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "transitioning to an invalid state" do
|
98
|
+
it "raises an argument error" do
|
99
|
+
expect { subject.transition :invalid_state }.to raise_error(ArgumentError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should not call transition! if the state is :default" do
|
103
|
+
subject.should_not_receive :transition!
|
104
|
+
subject.transition :default
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|