celluloid 0.13.0 → 0.14.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +1 -2
- data/lib/celluloid.rb +84 -32
- data/lib/celluloid/actor.rb +35 -30
- data/lib/celluloid/autostart.rb +0 -13
- data/lib/celluloid/calls.rb +71 -23
- data/lib/celluloid/core_ext.rb +3 -14
- data/lib/celluloid/cpu_counter.rb +1 -1
- data/lib/celluloid/evented_mailbox.rb +82 -0
- data/lib/celluloid/fsm.rb +2 -0
- data/lib/celluloid/future.rb +4 -4
- data/lib/celluloid/internal_pool.rb +11 -8
- data/lib/celluloid/legacy.rb +14 -13
- data/lib/celluloid/logging/incident_logger.rb +2 -2
- data/lib/celluloid/mailbox.rb +16 -0
- data/lib/celluloid/method.rb +7 -7
- data/lib/celluloid/notifications.rb +1 -1
- data/lib/celluloid/proxies/abstract_proxy.rb +1 -1
- data/lib/celluloid/proxies/actor_proxy.rb +23 -27
- data/lib/celluloid/proxies/async_proxy.rb +18 -6
- data/lib/celluloid/proxies/block_proxy.rb +29 -0
- data/lib/celluloid/proxies/future_proxy.rb +9 -3
- data/lib/celluloid/proxies/sync_proxy.rb +31 -0
- data/lib/celluloid/receivers.rb +1 -1
- data/lib/celluloid/responses.rb +13 -1
- data/lib/celluloid/stack_dump.rb +8 -5
- data/lib/celluloid/supervision_group.rb +6 -4
- data/lib/celluloid/tasks.rb +63 -2
- data/lib/celluloid/tasks/task_fiber.rb +6 -46
- data/lib/celluloid/tasks/task_thread.rb +8 -44
- data/lib/celluloid/thread.rb +82 -0
- data/lib/celluloid/thread_handle.rb +3 -2
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +116 -53
- data/spec/support/example_actor_class.rb +7 -1
- data/spec/support/mailbox_examples.rb +29 -3
- data/spec/support/task_examples.rb +11 -9
- metadata +21 -29
@@ -3,20 +3,32 @@ module Celluloid
|
|
3
3
|
class AsyncProxy < AbstractProxy
|
4
4
|
attr_reader :mailbox
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@mailbox, @klass =
|
6
|
+
def initialize(mailbox, klass)
|
7
|
+
@mailbox, @klass = mailbox, klass
|
8
8
|
end
|
9
9
|
|
10
10
|
def inspect
|
11
11
|
"#<Celluloid::AsyncProxy(#{@klass})>"
|
12
12
|
end
|
13
13
|
|
14
|
-
# method_missing black magic to call bang predicate methods asynchronously
|
15
14
|
def method_missing(meth, *args, &block)
|
16
15
|
if @mailbox == ::Thread.current[:celluloid_mailbox]
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
args.unshift meth
|
17
|
+
meth = :__send__
|
18
|
+
end
|
19
|
+
|
20
|
+
if block_given?
|
21
|
+
# FIXME: nicer exception
|
22
|
+
raise "Cannot use blocks with async yet"
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
@mailbox << AsyncCall.new(meth, args, block)
|
27
|
+
rescue MailboxError
|
28
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
29
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
30
|
+
# async calls should find other ways to deal with actors dying
|
31
|
+
# during an async call (i.e. linking/supervisors)
|
20
32
|
end
|
21
33
|
end
|
22
34
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Celluloid
|
2
|
+
class BlockProxy
|
3
|
+
def initialize(call, mailbox, block)
|
4
|
+
@call = call
|
5
|
+
@mailbox = mailbox
|
6
|
+
@block = block
|
7
|
+
@execution = :sender
|
8
|
+
end
|
9
|
+
attr_writer :execution
|
10
|
+
attr_reader :call, :block
|
11
|
+
|
12
|
+
def to_proc
|
13
|
+
if @execution == :sender
|
14
|
+
lambda do |*values|
|
15
|
+
if task = Thread.current[:celluloid_task]
|
16
|
+
@mailbox << BlockCall.new(self, Actor.current.mailbox, values)
|
17
|
+
# TODO: if respond fails, the Task will never be resumed
|
18
|
+
task.suspend(:invokeblock)
|
19
|
+
else
|
20
|
+
# FIXME: better exception
|
21
|
+
raise "No task to suspend"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -3,8 +3,8 @@ module Celluloid
|
|
3
3
|
class FutureProxy < AbstractProxy
|
4
4
|
attr_reader :mailbox
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@mailbox, @klass =
|
6
|
+
def initialize(mailbox, klass)
|
7
|
+
@mailbox, @klass = mailbox, klass
|
8
8
|
end
|
9
9
|
|
10
10
|
def inspect
|
@@ -13,7 +13,13 @@ 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 block_given?
|
17
|
+
# FIXME: nicer exception
|
18
|
+
raise "Cannot use blocks with futures yet"
|
19
|
+
end
|
20
|
+
future = Future.new
|
21
|
+
future.execute(@mailbox, meth, args, block)
|
22
|
+
future
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# A proxy which sends synchronous calls to an actor
|
3
|
+
class SyncProxy < AbstractProxy
|
4
|
+
attr_reader :mailbox
|
5
|
+
|
6
|
+
def initialize(mailbox, klass)
|
7
|
+
@mailbox, @klass = mailbox, klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
"#<Celluloid::SyncProxy(#{@klass})>"
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
if @mailbox == ::Thread.current[:celluloid_mailbox]
|
16
|
+
args.unshift meth
|
17
|
+
meth = :__send__
|
18
|
+
end
|
19
|
+
|
20
|
+
call = SyncCall.new(::Celluloid.mailbox, meth, args, block)
|
21
|
+
|
22
|
+
begin
|
23
|
+
@mailbox << call
|
24
|
+
rescue MailboxError
|
25
|
+
raise DeadActorError, "attempted to call a dead actor"
|
26
|
+
end
|
27
|
+
|
28
|
+
call.value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/celluloid/receivers.rb
CHANGED
data/lib/celluloid/responses.rb
CHANGED
@@ -15,7 +15,7 @@ module Celluloid
|
|
15
15
|
# Call completed successfully
|
16
16
|
class SuccessResponse < Response; end
|
17
17
|
|
18
|
-
# Call was aborted due to
|
18
|
+
# Call was aborted due to sender error
|
19
19
|
class ErrorResponse < Response
|
20
20
|
def value
|
21
21
|
ex = super
|
@@ -29,4 +29,16 @@ module Celluloid
|
|
29
29
|
raise ex
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
class BlockResponse
|
34
|
+
def initialize(call, result)
|
35
|
+
@call = call
|
36
|
+
@result = result
|
37
|
+
end
|
38
|
+
|
39
|
+
def dispatch
|
40
|
+
@call.task.resume(@result)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
32
44
|
end
|
data/lib/celluloid/stack_dump.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Celluloid
|
2
2
|
class StackDump
|
3
3
|
|
4
|
-
class TaskState < Struct.new(:task_class, :status)
|
4
|
+
class TaskState < Struct.new(:task_class, :status, :backtrace)
|
5
5
|
end
|
6
6
|
|
7
7
|
class ActorState
|
@@ -24,8 +24,9 @@ module Celluloid
|
|
24
24
|
|
25
25
|
def snapshot
|
26
26
|
Thread.list.each do |thread|
|
27
|
-
if
|
28
|
-
|
27
|
+
if thread.celluloid?
|
28
|
+
next if thread.task
|
29
|
+
@actors << snapshot_actor(thread.actor) if thread.actor
|
29
30
|
else
|
30
31
|
@threads << snapshot_thread(thread)
|
31
32
|
end
|
@@ -42,7 +43,7 @@ module Celluloid
|
|
42
43
|
state.status = :idle
|
43
44
|
else
|
44
45
|
state.status = :running
|
45
|
-
state.tasks = tasks.collect { |t| TaskState.new(t.class, t.status) }
|
46
|
+
state.tasks = tasks.collect { |t| TaskState.new(t.class, t.status, t.backtrace) }
|
46
47
|
end
|
47
48
|
|
48
49
|
state.backtrace = actor.thread.backtrace if actor.thread
|
@@ -62,16 +63,18 @@ module Celluloid
|
|
62
63
|
|
63
64
|
if actor.status == :idle
|
64
65
|
string << "State: Idle (waiting for messages)\n"
|
66
|
+
display_backtrace actor.backtrace, string
|
65
67
|
else
|
66
68
|
string << "State: Running (executing tasks)\n"
|
69
|
+
display_backtrace actor.backtrace, string
|
67
70
|
string << "Tasks:\n"
|
68
71
|
|
69
72
|
actor.tasks.each_with_index do |task, i|
|
70
73
|
string << " #{i+1}) #{task.task_class}: #{task.status}\n"
|
74
|
+
display_backtrace task.backtrace, string
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
74
|
-
display_backtrace actor.backtrace, string
|
75
78
|
output.print string
|
76
79
|
end
|
77
80
|
|
@@ -12,9 +12,9 @@ module Celluloid
|
|
12
12
|
|
13
13
|
# Start this application (and watch it with a supervisor)
|
14
14
|
def run!
|
15
|
-
group = new do |
|
15
|
+
group = new do |_group|
|
16
16
|
blocks.each do |block|
|
17
|
-
block.call(
|
17
|
+
block.call(_group)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
group
|
@@ -63,6 +63,8 @@ module Celluloid
|
|
63
63
|
yield current_actor if block_given?
|
64
64
|
end
|
65
65
|
|
66
|
+
execute_block_on_receiver :initialize, :supervise, :supervise_as
|
67
|
+
|
66
68
|
def supervise(klass, *args, &block)
|
67
69
|
add(klass, :args => args, :block => block)
|
68
70
|
end
|
@@ -95,8 +97,8 @@ module Celluloid
|
|
95
97
|
|
96
98
|
# Restart a crashed actor
|
97
99
|
def restart_actor(actor, reason)
|
98
|
-
member = @members.find do |
|
99
|
-
|
100
|
+
member = @members.find do |_member|
|
101
|
+
_member.actor == actor
|
100
102
|
end
|
101
103
|
raise "a group member went missing. This shouldn't be!" unless member
|
102
104
|
|
data/lib/celluloid/tasks.rb
CHANGED
@@ -19,10 +19,71 @@ module Celluloid
|
|
19
19
|
Task.current.suspend(status)
|
20
20
|
end
|
21
21
|
|
22
|
+
attr_reader :type, :status
|
23
|
+
|
22
24
|
# Create a new task
|
23
25
|
def initialize(type)
|
24
26
|
@type = type
|
25
27
|
@status = :new
|
28
|
+
|
29
|
+
actor = Thread.current[:celluloid_actor]
|
30
|
+
chain_id = Thread.current[:celluloid_chain_id]
|
31
|
+
|
32
|
+
raise NotActorError, "can't create tasks outside of actors" unless actor
|
33
|
+
|
34
|
+
create do
|
35
|
+
begin
|
36
|
+
@status = :running
|
37
|
+
actor.setup_thread
|
38
|
+
Thread.current[:celluloid_task] = self
|
39
|
+
Thread.current[:celluloid_chain_id] = chain_id
|
40
|
+
|
41
|
+
actor.tasks << self
|
42
|
+
yield
|
43
|
+
rescue Task::TerminatedError
|
44
|
+
# Task was explicitly terminated
|
45
|
+
ensure
|
46
|
+
@status = :dead
|
47
|
+
actor.tasks.delete self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create(&block)
|
53
|
+
raise "Implement #{self.class}#create"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Suspend the current task, changing the status to the given argument
|
57
|
+
def suspend(status)
|
58
|
+
@status = status
|
59
|
+
value = signal
|
60
|
+
|
61
|
+
raise value if value.is_a?(Task::TerminatedError)
|
62
|
+
@status = :running
|
63
|
+
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
# Resume a suspended task, giving it a value to return if needed
|
68
|
+
def resume(value = nil)
|
69
|
+
deliver(value)
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Terminate this task
|
74
|
+
def terminate
|
75
|
+
resume Task::TerminatedError.new("task was terminated") if running?
|
76
|
+
end
|
77
|
+
|
78
|
+
def backtrace
|
79
|
+
end
|
80
|
+
|
81
|
+
# Is the current task still running?
|
82
|
+
def running?; @status != :dead; end
|
83
|
+
|
84
|
+
# Nicer string inspect for tasks
|
85
|
+
def inspect
|
86
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
|
26
87
|
end
|
27
88
|
end
|
28
89
|
|
@@ -42,7 +103,7 @@ module Celluloid
|
|
42
103
|
end
|
43
104
|
|
44
105
|
def each(&blk)
|
45
|
-
@tasks.each
|
106
|
+
@tasks.each(&blk)
|
46
107
|
end
|
47
108
|
|
48
109
|
def first
|
@@ -56,4 +117,4 @@ module Celluloid
|
|
56
117
|
end
|
57
118
|
|
58
119
|
require 'celluloid/tasks/task_fiber'
|
59
|
-
require 'celluloid/tasks/task_thread'
|
120
|
+
require 'celluloid/tasks/task_thread'
|
@@ -3,52 +3,20 @@ module Celluloid
|
|
3
3
|
|
4
4
|
# Tasks with a Fiber backend
|
5
5
|
class TaskFiber < Task
|
6
|
-
attr_reader :type, :status
|
7
|
-
|
8
|
-
# Run the given block within a task
|
9
|
-
def initialize(type)
|
10
|
-
super
|
11
|
-
|
12
|
-
actor = Thread.current[:celluloid_actor]
|
13
|
-
mailbox = Thread.current[:celluloid_mailbox]
|
14
|
-
chain_id = Thread.current[:celluloid_chain_id]
|
15
|
-
|
16
|
-
raise NotActorError, "can't create tasks outside of actors" unless actor
|
17
6
|
|
7
|
+
def create
|
18
8
|
@fiber = Fiber.new do
|
19
|
-
|
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
|
-
|
25
|
-
actor.tasks << self
|
26
|
-
|
27
|
-
begin
|
28
|
-
yield
|
29
|
-
rescue Task::TerminatedError
|
30
|
-
# Task was explicitly terminated
|
31
|
-
ensure
|
32
|
-
@status = :dead
|
33
|
-
actor.tasks.delete self
|
34
|
-
end
|
9
|
+
yield
|
35
10
|
end
|
36
11
|
end
|
37
12
|
|
38
|
-
|
39
|
-
|
40
|
-
@status = status
|
41
|
-
result = Fiber.yield
|
42
|
-
raise result if result.is_a?(Task::TerminatedError)
|
43
|
-
@status = :running
|
44
|
-
|
45
|
-
result
|
13
|
+
def signal
|
14
|
+
Fiber.yield
|
46
15
|
end
|
47
16
|
|
48
17
|
# Resume a suspended task, giving it a value to return if needed
|
49
|
-
def
|
18
|
+
def deliver(value)
|
50
19
|
@fiber.resume value
|
51
|
-
nil
|
52
20
|
rescue SystemStackError => ex
|
53
21
|
raise FiberStackError, "#{ex} (please see https://github.com/celluloid/celluloid/wiki/Fiber-stack-errors)"
|
54
22
|
rescue FiberError => ex
|
@@ -57,17 +25,9 @@ module Celluloid
|
|
57
25
|
|
58
26
|
# Terminate this task
|
59
27
|
def terminate
|
60
|
-
|
28
|
+
super
|
61
29
|
rescue FiberError
|
62
30
|
# If we're getting this the task should already be dead
|
63
31
|
end
|
64
|
-
|
65
|
-
# Is the current task still running?
|
66
|
-
def running?; @fiber.alive?; end
|
67
|
-
|
68
|
-
# Nicer string inspect for tasks
|
69
|
-
def inspect
|
70
|
-
"<Celluloid::TaskFiber:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
|
71
|
-
end
|
72
32
|
end
|
73
33
|
end
|
@@ -1,62 +1,37 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Tasks with a Thread backend
|
3
3
|
class TaskThread < Task
|
4
|
-
attr_reader :type, :status
|
5
|
-
|
6
4
|
# Run the given block within a task
|
7
5
|
def initialize(type)
|
8
|
-
super
|
9
|
-
|
10
6
|
@resume_queue = Queue.new
|
11
7
|
@exception_queue = Queue.new
|
12
8
|
@yield_mutex = Mutex.new
|
13
9
|
@yield_cond = ConditionVariable.new
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
chain_id = Thread.current[:celluloid_chain_id]
|
18
|
-
|
19
|
-
raise NotActorError, "can't create tasks outside of actors" unless actor
|
11
|
+
super
|
12
|
+
end
|
20
13
|
|
14
|
+
def create
|
21
15
|
@thread = Celluloid.internal_pool.get do
|
22
16
|
begin
|
23
17
|
ex = @resume_queue.pop
|
24
18
|
raise ex if ex.is_a?(Task::TerminatedError)
|
25
19
|
|
26
|
-
@status = :running
|
27
|
-
Thread.current[:celluloid_actor] = actor
|
28
|
-
Thread.current[:celluloid_mailbox] = mailbox
|
29
|
-
Thread.current[:celluloid_task] = self
|
30
|
-
Thread.current[:celluloid_chain_id] = chain_id
|
31
|
-
|
32
|
-
actor.tasks << self
|
33
20
|
yield
|
34
|
-
rescue Task::TerminatedError
|
35
|
-
# Task was explicitly terminated
|
36
21
|
rescue Exception => ex
|
37
22
|
@exception_queue << ex
|
38
23
|
ensure
|
39
|
-
@status = :dead
|
40
|
-
actor.tasks.delete self
|
41
24
|
@yield_cond.signal
|
42
25
|
end
|
43
26
|
end
|
44
27
|
end
|
45
28
|
|
46
|
-
|
47
|
-
def suspend(status)
|
48
|
-
@status = status
|
29
|
+
def signal
|
49
30
|
@yield_cond.signal
|
50
|
-
|
51
|
-
|
52
|
-
raise value if value.is_a?(Task::TerminatedError)
|
53
|
-
@status = :running
|
54
|
-
|
55
|
-
value
|
31
|
+
@resume_queue.pop
|
56
32
|
end
|
57
33
|
|
58
|
-
|
59
|
-
def resume(value = nil)
|
34
|
+
def deliver(value)
|
60
35
|
raise DeadTaskError, "cannot resume a dead task" unless @thread.alive?
|
61
36
|
|
62
37
|
@yield_mutex.synchronize do
|
@@ -66,23 +41,12 @@ module Celluloid
|
|
66
41
|
raise @exception_queue.pop
|
67
42
|
end
|
68
43
|
end
|
69
|
-
|
70
|
-
nil
|
71
44
|
rescue ThreadError
|
72
45
|
raise DeadTaskError, "cannot resume a dead task"
|
73
46
|
end
|
74
47
|
|
75
|
-
|
76
|
-
|
77
|
-
resume Task::TerminatedError.new("task was terminated") if @thread.alive?
|
78
|
-
end
|
79
|
-
|
80
|
-
# Is the current task still running?
|
81
|
-
def running?; @status != :dead; end
|
82
|
-
|
83
|
-
# Nicer string inspect for tasks
|
84
|
-
def inspect
|
85
|
-
"<Celluloid::TaskThread:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
|
48
|
+
def backtrace
|
49
|
+
@thread.backtrace
|
86
50
|
end
|
87
51
|
end
|
88
52
|
end
|