celluloid 0.14.1 → 0.15.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|