celluloid 0.12.4 → 0.13.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -3
- data/lib/celluloid.rb +68 -98
- data/lib/celluloid/actor.rb +42 -22
- data/lib/celluloid/boot.rb +13 -0
- data/lib/celluloid/calls.rb +50 -8
- data/lib/celluloid/condition.rb +64 -0
- data/lib/celluloid/core_ext.rb +1 -1
- data/lib/celluloid/cpu_counter.rb +18 -0
- data/lib/celluloid/future.rb +4 -1
- data/lib/celluloid/internal_pool.rb +62 -44
- data/lib/celluloid/legacy.rb +46 -0
- data/lib/celluloid/links.rb +0 -5
- data/lib/celluloid/logger.rb +10 -4
- data/lib/celluloid/mailbox.rb +2 -1
- data/lib/celluloid/method.rb +5 -0
- data/lib/celluloid/pool_manager.rb +8 -7
- data/lib/celluloid/proxies/actor_proxy.rb +5 -8
- data/lib/celluloid/proxies/async_proxy.rb +5 -1
- data/lib/celluloid/responses.rb +8 -6
- data/lib/celluloid/stack_dump.rb +86 -0
- data/lib/celluloid/supervision_group.rb +4 -2
- data/lib/celluloid/system_events.rb +11 -0
- data/lib/celluloid/{task.rb → tasks.rb} +11 -5
- data/lib/celluloid/tasks/task_fiber.rb +11 -7
- data/lib/celluloid/tasks/task_thread.rb +18 -14
- data/lib/celluloid/thread_handle.rb +10 -6
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +118 -112
- data/spec/support/example_actor_class.rb +9 -5
- data/spec/support/task_examples.rb +2 -2
- metadata +15 -12
- data/lib/celluloid/stack_dumper.rb +0 -45
data/lib/celluloid/method.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Method handles that route through an actor proxy
|
3
3
|
class Method
|
4
|
+
|
4
5
|
def initialize(actor, name)
|
5
6
|
raise NameError, "undefined method `#{name}'" unless actor.respond_to? name
|
6
7
|
|
@@ -8,6 +9,10 @@ module Celluloid
|
|
8
9
|
@klass = @actor.class
|
9
10
|
end
|
10
11
|
|
12
|
+
def arity
|
13
|
+
@actor._send_(:method, @name).arity
|
14
|
+
end
|
15
|
+
|
11
16
|
def call(*args, &block)
|
12
17
|
@actor._send_(@name, *args, &block)
|
13
18
|
end
|
@@ -6,7 +6,8 @@ module Celluloid
|
|
6
6
|
# Don't use this class directly. Instead use MyKlass.pool
|
7
7
|
class PoolManager
|
8
8
|
include Celluloid
|
9
|
-
trap_exit :
|
9
|
+
trap_exit :__crash_handler__
|
10
|
+
finalizer :__shutdown__
|
10
11
|
|
11
12
|
def initialize(worker_class, options = {})
|
12
13
|
@size = options[:size] || [Celluloid.cores, 2].max
|
@@ -22,7 +23,7 @@ module Celluloid
|
|
22
23
|
@busy = []
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
+
def __shutdown__
|
26
27
|
terminators = (@idle + @busy).each do |actor|
|
27
28
|
begin
|
28
29
|
actor.future(:terminate)
|
@@ -34,13 +35,13 @@ module Celluloid
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def _send_(method, *args, &block)
|
37
|
-
worker =
|
38
|
+
worker = __provision_worker__
|
38
39
|
|
39
40
|
begin
|
40
41
|
worker._send_ method, *args, &block
|
41
42
|
rescue DeadActorError # if we get a dead actor out of the pool
|
42
43
|
wait :respawn_complete
|
43
|
-
worker =
|
44
|
+
worker = __provision_worker__
|
44
45
|
retry
|
45
46
|
rescue Exception => ex
|
46
47
|
abort ex
|
@@ -89,11 +90,11 @@ module Celluloid
|
|
89
90
|
end
|
90
91
|
|
91
92
|
# Provision a new worker
|
92
|
-
def
|
93
|
+
def __provision_worker__
|
93
94
|
while @idle.empty?
|
94
95
|
# Wait for responses from one of the busy workers
|
95
96
|
response = exclusive { receive { |msg| msg.is_a?(Response) } }
|
96
|
-
Thread.current[:
|
97
|
+
Thread.current[:celluloid_actor].handle_message(response)
|
97
98
|
end
|
98
99
|
|
99
100
|
worker = @idle.shift
|
@@ -103,7 +104,7 @@ module Celluloid
|
|
103
104
|
end
|
104
105
|
|
105
106
|
# Spawn a new worker for every crashed one
|
106
|
-
def
|
107
|
+
def __crash_handler__(actor, reason)
|
107
108
|
@busy.delete actor
|
108
109
|
@idle.delete actor
|
109
110
|
return unless reason
|
@@ -18,6 +18,10 @@ module Celluloid
|
|
18
18
|
Actor.call @mailbox, :__send__, :class
|
19
19
|
end
|
20
20
|
|
21
|
+
def send(meth, *args, &block)
|
22
|
+
Actor.call @mailbox, :send, meth, *args, &block
|
23
|
+
end
|
24
|
+
|
21
25
|
def _send_(meth, *args, &block)
|
22
26
|
Actor.call @mailbox, :__send__, meth, *args, &block
|
23
27
|
end
|
@@ -93,14 +97,7 @@ module Celluloid
|
|
93
97
|
|
94
98
|
# method_missing black magic to call bang predicate methods asynchronously
|
95
99
|
def method_missing(meth, *args, &block)
|
96
|
-
|
97
|
-
if meth.match(/!$/)
|
98
|
-
unbanged_meth = meth.to_s
|
99
|
-
unbanged_meth.slice!(-1, 1)
|
100
|
-
Actor.async @mailbox, unbanged_meth, *args, &block
|
101
|
-
else
|
102
|
-
Actor.call @mailbox, meth, *args, &block
|
103
|
-
end
|
100
|
+
Actor.call @mailbox, meth, *args, &block
|
104
101
|
end
|
105
102
|
end
|
106
103
|
end
|
@@ -13,7 +13,11 @@ module Celluloid
|
|
13
13
|
|
14
14
|
# method_missing black magic to call bang predicate methods asynchronously
|
15
15
|
def method_missing(meth, *args, &block)
|
16
|
-
|
16
|
+
if @mailbox == ::Thread.current[:celluloid_mailbox]
|
17
|
+
Actor.async @mailbox, :__send__, meth, *args, &block
|
18
|
+
else
|
19
|
+
Actor.async @mailbox, meth, *args, &block
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
data/lib/celluloid/responses.rb
CHANGED
@@ -18,13 +18,15 @@ module Celluloid
|
|
18
18
|
# Call was aborted due to caller error
|
19
19
|
class ErrorResponse < Response
|
20
20
|
def value
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
ex = super
|
22
|
+
ex = ex.cause if ex.is_a? AbortError
|
23
|
+
|
24
|
+
if ex.backtrace
|
25
|
+
ex.backtrace << "(celluloid):0:in `remote procedure call'"
|
26
|
+
ex.backtrace.concat(caller)
|
27
27
|
end
|
28
|
+
|
29
|
+
raise ex
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Celluloid
|
2
|
+
class StackDump
|
3
|
+
|
4
|
+
class TaskState < Struct.new(:task_class, :status)
|
5
|
+
end
|
6
|
+
|
7
|
+
class ActorState
|
8
|
+
attr_accessor :subject_id, :subject_class, :name
|
9
|
+
attr_accessor :status, :tasks
|
10
|
+
attr_accessor :backtrace
|
11
|
+
end
|
12
|
+
|
13
|
+
class ThreadState < Struct.new(:thread_id, :backtrace)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :actors, :threads
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@actors = []
|
20
|
+
@threads = []
|
21
|
+
|
22
|
+
snapshot
|
23
|
+
end
|
24
|
+
|
25
|
+
def snapshot
|
26
|
+
Thread.list.each do |thread|
|
27
|
+
if actor = thread[:celluloid_actor]
|
28
|
+
@actors << snapshot_actor(actor)
|
29
|
+
else
|
30
|
+
@threads << snapshot_thread(thread)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def snapshot_actor(actor)
|
36
|
+
state = ActorState.new
|
37
|
+
state.subject_id = actor.subject.object_id
|
38
|
+
state.subject_class = actor.subject.class
|
39
|
+
|
40
|
+
tasks = actor.tasks
|
41
|
+
if tasks.empty?
|
42
|
+
state.status = :idle
|
43
|
+
else
|
44
|
+
state.status = :running
|
45
|
+
state.tasks = tasks.collect { |t| TaskState.new(t.class, t.status) }
|
46
|
+
end
|
47
|
+
|
48
|
+
state.backtrace = actor.thread.backtrace if actor.thread
|
49
|
+
state
|
50
|
+
end
|
51
|
+
|
52
|
+
def snapshot_thread(thread)
|
53
|
+
ThreadState.new(thread.object_id, thread.backtrace)
|
54
|
+
end
|
55
|
+
|
56
|
+
def dump(output = STDERR)
|
57
|
+
@actors.each do |actor|
|
58
|
+
output << "Celluloid::Actor 0x#{actor.subject_id.to_s(16)}: #{actor.subject_class}"
|
59
|
+
output << " [#{actor.name}]" if actor.name
|
60
|
+
output << "\n"
|
61
|
+
|
62
|
+
if actor.status == :idle
|
63
|
+
output << "State: Idle (waiting for messages)\n"
|
64
|
+
else
|
65
|
+
output << "State: Running (executing tasks)\n"
|
66
|
+
output << "Tasks:\n"
|
67
|
+
|
68
|
+
actor.tasks.each_with_index do |task, i|
|
69
|
+
output << " #{i+1}) #{task.task_class}: #{task.status}\n"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
display_backtrace actor.backtrace, output
|
74
|
+
end
|
75
|
+
|
76
|
+
@threads.each do |thread|
|
77
|
+
output << "Thread 0x#{thread.thread_id.to_s(16)}:\n"
|
78
|
+
display_backtrace thread.backtrace, output
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def display_backtrace(backtrace, output)
|
83
|
+
output << "\t" << backtrace.join("\n\t") << "\n\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -86,9 +86,11 @@ module Celluloid
|
|
86
86
|
@members.map(&:actor)
|
87
87
|
end
|
88
88
|
|
89
|
+
finalizer :finalize
|
90
|
+
|
89
91
|
# Terminate the group
|
90
92
|
def finalize
|
91
|
-
@members.
|
93
|
+
@members.reverse_each(&:terminate)
|
92
94
|
end
|
93
95
|
|
94
96
|
# Restart a crashed actor
|
@@ -147,7 +149,7 @@ module Celluloid
|
|
147
149
|
|
148
150
|
def terminate
|
149
151
|
@actor.terminate if @actor
|
150
|
-
rescue DeadActorError
|
152
|
+
rescue DeadActorError, MailboxError
|
151
153
|
end
|
152
154
|
end
|
153
155
|
end
|
@@ -51,4 +51,15 @@ module Celluloid
|
|
51
51
|
|
52
52
|
# Request for an actor to terminate
|
53
53
|
class TerminationRequest < SystemEvent; end
|
54
|
+
|
55
|
+
# Signal a condition
|
56
|
+
class SignalConditionRequest < SystemEvent
|
57
|
+
def initialize(task, value)
|
58
|
+
@task, @value = task, value
|
59
|
+
end
|
60
|
+
|
61
|
+
def call
|
62
|
+
@task.resume(@value)
|
63
|
+
end
|
64
|
+
end
|
54
65
|
end
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'celluloid/tasks/task_fiber'
|
2
|
-
require 'celluloid/tasks/task_thread'
|
3
|
-
|
4
1
|
module Celluloid
|
5
2
|
# Asked to do task-related things outside a task
|
6
3
|
class NotTaskError < StandardError; end
|
@@ -9,17 +6,26 @@ module Celluloid
|
|
9
6
|
class DeadTaskError < StandardError; end
|
10
7
|
|
11
8
|
# Tasks are interruptable/resumable execution contexts used to run methods
|
12
|
-
|
9
|
+
class Task
|
13
10
|
class TerminatedError < StandardError; end # kill a running task
|
14
11
|
|
15
12
|
# Obtain the current task
|
16
13
|
def self.current
|
17
|
-
Thread.current[:
|
14
|
+
Thread.current[:celluloid_task] or raise NotTaskError, "not within a task context"
|
18
15
|
end
|
19
16
|
|
20
17
|
# Suspend the running task, deferring to the scheduler
|
21
18
|
def self.suspend(status)
|
22
19
|
Task.current.suspend(status)
|
23
20
|
end
|
21
|
+
|
22
|
+
# Create a new task
|
23
|
+
def initialize(type)
|
24
|
+
@type = type
|
25
|
+
@status = :new
|
26
|
+
end
|
24
27
|
end
|
25
28
|
end
|
29
|
+
|
30
|
+
require 'celluloid/tasks/task_fiber'
|
31
|
+
require 'celluloid/tasks/task_thread'
|
@@ -2,22 +2,26 @@ module Celluloid
|
|
2
2
|
class FiberStackError < StandardError; end
|
3
3
|
|
4
4
|
# Tasks with a Fiber backend
|
5
|
-
class TaskFiber
|
5
|
+
class TaskFiber < Task
|
6
6
|
attr_reader :type, :status
|
7
7
|
|
8
8
|
# Run the given block within a task
|
9
9
|
def initialize(type)
|
10
|
-
|
11
|
-
|
10
|
+
super
|
11
|
+
|
12
|
+
actor = Thread.current[:celluloid_actor]
|
13
|
+
mailbox = Thread.current[:celluloid_mailbox]
|
14
|
+
chain_id = Thread.current[:celluloid_chain_id]
|
12
15
|
|
13
|
-
actor, mailbox = Thread.current[:actor], Thread.current[:mailbox]
|
14
16
|
raise NotActorError, "can't create tasks outside of actors" unless actor
|
15
17
|
|
16
18
|
@fiber = Fiber.new do
|
17
19
|
@status = :running
|
18
|
-
Thread.current[:
|
19
|
-
Thread.current[:
|
20
|
-
Thread.current[:
|
20
|
+
Thread.current[:celluloid_actor] = actor
|
21
|
+
Thread.current[:celluloid_mailbox] = mailbox
|
22
|
+
Thread.current[:celluloid_task] = self
|
23
|
+
Thread.current[:celluloid_chain_id] = chain_id
|
24
|
+
|
21
25
|
actor.tasks << self
|
22
26
|
|
23
27
|
begin
|
@@ -1,31 +1,35 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Tasks with a Thread backend
|
3
|
-
class TaskThread
|
3
|
+
class TaskThread < Task
|
4
4
|
attr_reader :type, :status
|
5
5
|
|
6
6
|
# Run the given block within a task
|
7
7
|
def initialize(type)
|
8
|
-
|
9
|
-
@status = :new
|
8
|
+
super
|
10
9
|
|
11
10
|
@resume_queue = Queue.new
|
12
11
|
@yield_mutex = Mutex.new
|
13
12
|
@yield_cond = ConditionVariable.new
|
14
13
|
|
15
|
-
actor
|
14
|
+
actor = Thread.current[:celluloid_actor]
|
15
|
+
mailbox = Thread.current[:celluloid_mailbox]
|
16
|
+
chain_id = Thread.current[:celluloid_chain_id]
|
17
|
+
|
16
18
|
raise NotActorError, "can't create tasks outside of actors" unless actor
|
17
19
|
|
18
|
-
@thread =
|
20
|
+
@thread = Celluloid.internal_pool.get do
|
19
21
|
begin
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
ex = @resume_queue.pop.is_a?(Task::TerminatedError)
|
23
|
+
raise ex if ex
|
24
|
+
|
25
|
+
@status = :running
|
26
|
+
Thread.current[:celluloid_actor] = actor
|
27
|
+
Thread.current[:celluloid_mailbox] = mailbox
|
28
|
+
Thread.current[:celluloid_task] = self
|
29
|
+
Thread.current[:celluloid_chain_id] = chain_id
|
30
|
+
|
31
|
+
actor.tasks << self
|
32
|
+
yield
|
29
33
|
rescue Task::TerminatedError
|
30
34
|
# Task was explicitly terminated
|
31
35
|
ensure
|
@@ -1,18 +1,13 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
1
|
module Celluloid
|
4
2
|
# An abstraction around threads from the InternalPool which ensures we don't
|
5
3
|
# accidentally do things to threads which have been returned to the pool,
|
6
4
|
# such as, say, killing them
|
7
5
|
class ThreadHandle
|
8
|
-
extend Forwardable
|
9
|
-
def_delegators :@thread, :backtrace
|
10
|
-
|
11
6
|
def initialize
|
12
7
|
@mutex = Mutex.new
|
13
8
|
@join = ConditionVariable.new
|
14
9
|
|
15
|
-
@thread =
|
10
|
+
@thread = Celluloid.internal_pool.get do
|
16
11
|
begin
|
17
12
|
yield
|
18
13
|
ensure
|
@@ -40,5 +35,14 @@ module Celluloid
|
|
40
35
|
@mutex.synchronize { @join.wait(@mutex) if @thread }
|
41
36
|
self
|
42
37
|
end
|
38
|
+
|
39
|
+
# Obtain the backtrace for this thread
|
40
|
+
def backtrace
|
41
|
+
@thread.backtrace
|
42
|
+
rescue NoMethodError
|
43
|
+
# undefined method `backtrace' for nil:NilClass
|
44
|
+
# Swallow this in case this ThreadHandle was terminated and @thread was
|
45
|
+
# set to nil
|
46
|
+
end
|
43
47
|
end
|
44
48
|
end
|
data/lib/celluloid/version.rb
CHANGED
@@ -30,11 +30,27 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
30
30
|
actor.greet.should == "Hi, I'm Troy McClure"
|
31
31
|
end
|
32
32
|
|
33
|
+
it "supports synchronous calls with blocks" do
|
34
|
+
actor = actor_class.new "Blocky Ralboa"
|
35
|
+
|
36
|
+
block_executed = false
|
37
|
+
actor.run { block_executed = true }
|
38
|
+
block_executed.should be_true
|
39
|
+
end
|
40
|
+
|
33
41
|
it "supports synchronous calls via #method" do
|
34
42
|
method = actor_class.new("Troy McClure").method(:greet)
|
35
43
|
method.call.should == "Hi, I'm Troy McClure"
|
36
44
|
end
|
37
45
|
|
46
|
+
it "supports #arity calls via #method" do
|
47
|
+
method = actor_class.new("Troy McClure").method(:greet)
|
48
|
+
method.arity.should == 0
|
49
|
+
|
50
|
+
method = actor_class.new("Troy McClure").method(:change_name)
|
51
|
+
method.arity.should == 1
|
52
|
+
end
|
53
|
+
|
38
54
|
it "supports future(:method) syntax for synchronous future calls" do
|
39
55
|
actor = actor_class.new "Troy McClure"
|
40
56
|
future = actor.future :greet
|
@@ -77,75 +93,11 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
77
93
|
actor.respond_to?(:zomg_private, true).should be_true
|
78
94
|
end
|
79
95
|
|
80
|
-
it "
|
81
|
-
actor = actor_class.new "
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end.to raise_exception(NoMethodError)
|
86
|
-
end
|
87
|
-
|
88
|
-
it "reraises exceptions which occur during synchronous calls in the caller" do
|
89
|
-
actor = actor_class.new "James Dean" # is this in bad taste?
|
90
|
-
|
91
|
-
expect do
|
92
|
-
actor.crash
|
93
|
-
end.to raise_exception(ExampleCrash)
|
94
|
-
end
|
95
|
-
|
96
|
-
describe "when #abort is called" do
|
97
|
-
it "raises exceptions in the caller but keeps running" do
|
98
|
-
actor = actor_class.new "Al Pacino"
|
99
|
-
|
100
|
-
e = nil
|
101
|
-
line_no = nil
|
102
|
-
|
103
|
-
expect do
|
104
|
-
begin
|
105
|
-
line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
|
106
|
-
rescue => ex
|
107
|
-
e = ex
|
108
|
-
raise
|
109
|
-
end
|
110
|
-
end.to raise_exception(ExampleCrash, "You die motherfucker!")
|
111
|
-
|
112
|
-
e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
|
113
|
-
e.foo.should be == :bar # Check the exception maintains instance variables
|
114
|
-
|
115
|
-
actor.should be_alive
|
116
|
-
end
|
117
|
-
|
118
|
-
it "converts strings to runtime errors" do
|
119
|
-
actor = actor_class.new "Al Pacino"
|
120
|
-
expect do
|
121
|
-
actor.crash_with_abort_raw "foo"
|
122
|
-
end.to raise_exception(RuntimeError, "foo")
|
123
|
-
end
|
124
|
-
|
125
|
-
it "crashes the caller if we pass neither String nor Exception" do
|
126
|
-
actor = actor_class.new "Al Pacino"
|
127
|
-
expect do
|
128
|
-
actor.crash_with_abort_raw 10
|
129
|
-
end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
|
130
|
-
|
131
|
-
actor.greet rescue nil # Ensure our actor has died.
|
132
|
-
actor.should_not be_alive
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
it "raises DeadActorError if methods are synchronously called on a dead actor" do
|
137
|
-
actor = actor_class.new "James Dean"
|
138
|
-
actor.crash rescue nil
|
139
|
-
|
140
|
-
expect do
|
141
|
-
actor.greet
|
142
|
-
end.to raise_exception(Celluloid::DeadActorError)
|
143
|
-
end
|
144
|
-
|
145
|
-
it "supports method! syntax for asynchronous calls" do
|
146
|
-
actor = actor_class.new "Troy McClure"
|
147
|
-
actor.change_name! "Charlie Sheen"
|
148
|
-
actor.greet.should == "Hi, I'm Charlie Sheen"
|
96
|
+
it "calls the user defined finalizer" do
|
97
|
+
actor = actor_class.new "Mr. Bean"
|
98
|
+
actor.wrapped_object.should_receive(:my_finalizer)
|
99
|
+
actor.terminate
|
100
|
+
Celluloid::Actor.join(actor)
|
149
101
|
end
|
150
102
|
|
151
103
|
it "supports async(:method) syntax for asynchronous calls" do
|
@@ -160,19 +112,13 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
160
112
|
actor.greet.should == "Hi, I'm Charlie Sheen"
|
161
113
|
end
|
162
114
|
|
163
|
-
it "supports method! syntax for asynchronous calls to itself" do
|
164
|
-
actor = actor_class.new "Troy McClure"
|
165
|
-
actor.change_name_with_a_bang "Charlie Sheen"
|
166
|
-
actor.greet.should == "Hi, I'm Charlie Sheen"
|
167
|
-
end
|
168
|
-
|
169
115
|
it "supports async.method syntax for asynchronous calls to itself" do
|
170
116
|
actor = actor_class.new "Troy McClure"
|
171
117
|
actor.change_name_async "Charlie Sheen"
|
172
118
|
actor.greet.should == "Hi, I'm Charlie Sheen"
|
173
119
|
end
|
174
120
|
|
175
|
-
it "allows an actor to call private methods asynchronously
|
121
|
+
it "allows an actor to call private methods asynchronously" do
|
176
122
|
actor = actor_class.new "Troy McClure"
|
177
123
|
actor.call_private
|
178
124
|
actor.private_called.should be_true
|
@@ -188,7 +134,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
188
134
|
|
189
135
|
it "inspects properly" do
|
190
136
|
actor = actor_class.new "Troy McClure"
|
191
|
-
actor.inspect.should match(/Celluloid::
|
137
|
+
actor.inspect.should match(/Celluloid::ActorProxy\(/)
|
192
138
|
actor.inspect.should match(/#{actor_class}/)
|
193
139
|
actor.inspect.should include('@name="Troy McClure"')
|
194
140
|
actor.inspect.should_not include("@celluloid")
|
@@ -215,6 +161,11 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
215
161
|
actor.wrapped_object.inspect.should include Celluloid::BARE_OBJECT_WARNING_MESSAGE
|
216
162
|
end
|
217
163
|
|
164
|
+
it "can override #send" do
|
165
|
+
actor = actor_class.new "Troy McClure"
|
166
|
+
actor.send('foo').should == 'oof'
|
167
|
+
end
|
168
|
+
|
218
169
|
context "mocking methods" do
|
219
170
|
let(:actor) { actor_class.new "Troy McClure" }
|
220
171
|
|
@@ -231,6 +182,82 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
231
182
|
end
|
232
183
|
end
|
233
184
|
|
185
|
+
context :exceptions do
|
186
|
+
it "reraises exceptions which occur during synchronous calls in the caller" do
|
187
|
+
actor = actor_class.new "James Dean" # is this in bad taste?
|
188
|
+
|
189
|
+
expect do
|
190
|
+
actor.crash
|
191
|
+
end.to raise_exception(ExampleCrash)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "includes both caller and receiver in exception traces" do
|
195
|
+
ExampleReceiver = Class.new do
|
196
|
+
include included_module
|
197
|
+
|
198
|
+
def receiver_method
|
199
|
+
raise ExampleCrash, "the spec purposely crashed me :("
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
ExampleCaller = Class.new do
|
204
|
+
include included_module
|
205
|
+
|
206
|
+
def caller_method
|
207
|
+
ExampleReceiver.new.receiver_method
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
ex = nil
|
212
|
+
begin
|
213
|
+
ExampleCaller.new.caller_method
|
214
|
+
rescue => ex
|
215
|
+
end
|
216
|
+
|
217
|
+
ex.should be_a ExampleCrash
|
218
|
+
ex.backtrace.grep(/`caller_method'/).should be_true
|
219
|
+
ex.backtrace.grep(/`receiver_method'/).should be_true
|
220
|
+
end
|
221
|
+
|
222
|
+
it "raises DeadActorError if methods are synchronously called on a dead actor" do
|
223
|
+
actor = actor_class.new "James Dean"
|
224
|
+
actor.crash rescue nil
|
225
|
+
|
226
|
+
expect do
|
227
|
+
actor.greet
|
228
|
+
end.to raise_exception(Celluloid::DeadActorError)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context :abort do
|
233
|
+
it "raises exceptions in the caller but keeps running" do
|
234
|
+
actor = actor_class.new "Al Pacino"
|
235
|
+
|
236
|
+
expect do
|
237
|
+
actor.crash_with_abort "You die motherfucker!", :bar
|
238
|
+
end.to raise_exception(ExampleCrash, "You die motherfucker!")
|
239
|
+
|
240
|
+
actor.should be_alive
|
241
|
+
end
|
242
|
+
|
243
|
+
it "converts strings to runtime errors" do
|
244
|
+
actor = actor_class.new "Al Pacino"
|
245
|
+
expect do
|
246
|
+
actor.crash_with_abort_raw "foo"
|
247
|
+
end.to raise_exception(RuntimeError, "foo")
|
248
|
+
end
|
249
|
+
|
250
|
+
it "crashes the caller if we pass neither String nor Exception" do
|
251
|
+
actor = actor_class.new "Al Pacino"
|
252
|
+
expect do
|
253
|
+
actor.crash_with_abort_raw 10
|
254
|
+
end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
|
255
|
+
|
256
|
+
actor.greet rescue nil # Ensure our actor has died.
|
257
|
+
actor.should_not be_alive
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
234
261
|
context :termination do
|
235
262
|
it "terminates" do
|
236
263
|
actor = actor_class.new "Arnold Schwarzenegger"
|
@@ -240,7 +267,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
240
267
|
actor.should_not be_alive
|
241
268
|
end
|
242
269
|
|
243
|
-
it "kills" do
|
270
|
+
it "kills" do # THOU SHALT ALWAYS KILL
|
244
271
|
actor = actor_class.new "Woody Harrelson"
|
245
272
|
actor.should be_alive
|
246
273
|
Celluloid::Actor.kill(actor)
|
@@ -270,7 +297,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
270
297
|
context :current_actor do
|
271
298
|
it "knows the current actor" do
|
272
299
|
actor = actor_class.new "Roger Daltrey"
|
273
|
-
actor.current_actor.should
|
300
|
+
actor.current_actor.should eq actor
|
274
301
|
end
|
275
302
|
|
276
303
|
it "raises NotActorError if called outside an actor" do
|
@@ -420,7 +447,7 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
420
447
|
obj = @signaler.new
|
421
448
|
obj.should_not be_signaled
|
422
449
|
|
423
|
-
obj.wait_for_signal
|
450
|
+
obj.async.wait_for_signal
|
424
451
|
obj.should_not be_signaled
|
425
452
|
|
426
453
|
obj.send_signal :foobar
|
@@ -533,8 +560,8 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
533
560
|
|
534
561
|
it "executes two methods in an exclusive order" do
|
535
562
|
actor = subject.new
|
536
|
-
actor.eat_donuts
|
537
|
-
actor.drink_coffee
|
563
|
+
actor.async.eat_donuts
|
564
|
+
actor.async.drink_coffee
|
538
565
|
sleep Celluloid::TIMER_QUANTUM * 2
|
539
566
|
actor.tasks.should == ['donuts', 'coffee']
|
540
567
|
end
|
@@ -574,7 +601,6 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
574
601
|
end
|
575
602
|
|
576
603
|
context :timers do
|
577
|
-
|
578
604
|
before do
|
579
605
|
@klass = Class.new do
|
580
606
|
include included_module
|
@@ -716,33 +742,13 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
716
742
|
end
|
717
743
|
end
|
718
744
|
|
719
|
-
context :
|
720
|
-
class ExampleProxy < Celluloid::ActorProxy; end
|
721
|
-
|
722
|
-
subject do
|
723
|
-
Class.new do
|
724
|
-
include included_module
|
725
|
-
proxy_class ExampleProxy
|
726
|
-
end
|
727
|
-
end
|
728
|
-
|
729
|
-
it "uses user-specified proxy" do
|
730
|
-
subject.new.__class__.should == ExampleProxy
|
731
|
-
end
|
732
|
-
|
733
|
-
it "retains custom proxy when subclassed" do
|
734
|
-
subclass = Class.new(subject)
|
735
|
-
subclass.new.__class__.should == ExampleProxy
|
736
|
-
end
|
737
|
-
end
|
738
|
-
|
739
|
-
context :use_mailbox do
|
745
|
+
context :mailbox_class do
|
740
746
|
class ExampleMailbox < Celluloid::Mailbox; end
|
741
747
|
|
742
748
|
subject do
|
743
749
|
Class.new do
|
744
750
|
include included_module
|
745
|
-
|
751
|
+
mailbox_class ExampleMailbox
|
746
752
|
end
|
747
753
|
end
|
748
754
|
|
@@ -756,23 +762,23 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
756
762
|
end
|
757
763
|
end
|
758
764
|
|
759
|
-
context :
|
760
|
-
class
|
765
|
+
context :proxy_class do
|
766
|
+
class ExampleProxy < Celluloid::ActorProxy; end
|
761
767
|
|
762
768
|
subject do
|
763
769
|
Class.new do
|
764
770
|
include included_module
|
765
|
-
|
771
|
+
proxy_class ExampleProxy
|
766
772
|
end
|
767
773
|
end
|
768
774
|
|
769
|
-
it "
|
770
|
-
subject.new.
|
775
|
+
it "uses user-specified proxy" do
|
776
|
+
subject.new.__class__.should == ExampleProxy
|
771
777
|
end
|
772
778
|
|
773
|
-
it "retains custom
|
779
|
+
it "retains custom proxy when subclassed" do
|
774
780
|
subclass = Class.new(subject)
|
775
|
-
subclass.new.
|
781
|
+
subclass.new.__class__.should == ExampleProxy
|
776
782
|
end
|
777
783
|
end
|
778
784
|
|