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
@@ -0,0 +1,24 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Properties define inheritable attributes of classes, somewhat similar to
|
3
|
+
# Rails cattr_*/mattr_* or class_attribute
|
4
|
+
module Properties
|
5
|
+
def property(name, opts = {})
|
6
|
+
default = opts.fetch(:default, nil)
|
7
|
+
multi = opts.fetch(:multi, false)
|
8
|
+
ivar_name = "@#{name}".to_sym
|
9
|
+
|
10
|
+
ancestors.first.send(:define_singleton_method, name) do |value = nil, *extra|
|
11
|
+
if value
|
12
|
+
value = value ? [value, *extra] : [] if multi
|
13
|
+
instance_variable_set(ivar_name, value)
|
14
|
+
elsif instance_variables.include?(ivar_name)
|
15
|
+
instance_variable_get(ivar_name)
|
16
|
+
elsif superclass.respond_to? name
|
17
|
+
superclass.send(name)
|
18
|
+
else
|
19
|
+
default
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Base class of all Celluloid proxies
|
3
3
|
class AbstractProxy < BasicObject
|
4
|
+
# Used for reflecting on proxy objects themselves
|
5
|
+
def __class__; AbstractProxy; end
|
6
|
+
|
4
7
|
# Needed for storing proxies in data structures
|
5
8
|
needed = [:object_id, :__id__, :hash] - instance_methods
|
6
9
|
if needed.any?
|
@@ -4,6 +4,9 @@ module Celluloid
|
|
4
4
|
class ActorProxy < SyncProxy
|
5
5
|
attr_reader :thread
|
6
6
|
|
7
|
+
# Used for reflecting on proxy objects themselves
|
8
|
+
def __class__; ActorProxy; end
|
9
|
+
|
7
10
|
def initialize(actor)
|
8
11
|
@thread = actor.thread
|
9
12
|
|
@@ -13,14 +16,6 @@ module Celluloid
|
|
13
16
|
@future_proxy = FutureProxy.new(@mailbox, @klass)
|
14
17
|
end
|
15
18
|
|
16
|
-
def class
|
17
|
-
method_missing :__send__, :class
|
18
|
-
end
|
19
|
-
|
20
|
-
def send(meth, *args, &block)
|
21
|
-
method_missing :send, meth, *args, &block
|
22
|
-
end
|
23
|
-
|
24
19
|
def _send_(meth, *args, &block)
|
25
20
|
method_missing :__send__, meth, *args, &block
|
26
21
|
end
|
@@ -31,26 +26,6 @@ module Celluloid
|
|
31
26
|
"#<Celluloid::ActorProxy(#{@klass}) dead>"
|
32
27
|
end
|
33
28
|
|
34
|
-
def name
|
35
|
-
method_missing :name
|
36
|
-
end
|
37
|
-
|
38
|
-
def is_a?(klass)
|
39
|
-
method_missing :is_a?, klass
|
40
|
-
end
|
41
|
-
|
42
|
-
def kind_of?(klass)
|
43
|
-
method_missing :kind_of?, klass
|
44
|
-
end
|
45
|
-
|
46
|
-
def respond_to?(meth, include_private = false)
|
47
|
-
method_missing :respond_to?, meth, include_private
|
48
|
-
end
|
49
|
-
|
50
|
-
def methods(include_ancestors = true)
|
51
|
-
method_missing :methods, include_ancestors
|
52
|
-
end
|
53
|
-
|
54
29
|
def method(name)
|
55
30
|
Method.new(self, name)
|
56
31
|
end
|
@@ -59,10 +34,6 @@ module Celluloid
|
|
59
34
|
@mailbox.alive?
|
60
35
|
end
|
61
36
|
|
62
|
-
def to_s
|
63
|
-
method_missing :to_s
|
64
|
-
end
|
65
|
-
|
66
37
|
alias_method :sync, :method_missing
|
67
38
|
|
68
39
|
# Obtain an async proxy or explicitly invoke a named async method
|
@@ -3,6 +3,9 @@ module Celluloid
|
|
3
3
|
class AsyncProxy < AbstractProxy
|
4
4
|
attr_reader :mailbox
|
5
5
|
|
6
|
+
# Used for reflecting on proxy objects themselves
|
7
|
+
def __class__; AsyncProxy; end
|
8
|
+
|
6
9
|
def initialize(mailbox, klass)
|
7
10
|
@mailbox, @klass = mailbox, klass
|
8
11
|
end
|
@@ -22,14 +25,7 @@ module Celluloid
|
|
22
25
|
raise "Cannot use blocks with async yet"
|
23
26
|
end
|
24
27
|
|
25
|
-
|
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)
|
32
|
-
end
|
28
|
+
@mailbox << AsyncCall.new(meth, args, block)
|
33
29
|
end
|
34
30
|
end
|
35
31
|
end
|
@@ -3,6 +3,9 @@ module Celluloid
|
|
3
3
|
class FutureProxy < AbstractProxy
|
4
4
|
attr_reader :mailbox
|
5
5
|
|
6
|
+
# Used for reflecting on proxy objects themselves
|
7
|
+
def __class__; FutureProxy; end
|
8
|
+
|
6
9
|
def initialize(mailbox, klass)
|
7
10
|
@mailbox, @klass = mailbox, klass
|
8
11
|
end
|
@@ -11,8 +14,11 @@ module Celluloid
|
|
11
14
|
"#<Celluloid::FutureProxy(#{@klass})>"
|
12
15
|
end
|
13
16
|
|
14
|
-
# method_missing black magic to call bang predicate methods asynchronously
|
15
17
|
def method_missing(meth, *args, &block)
|
18
|
+
unless @mailbox.alive?
|
19
|
+
raise DeadActorError, "attempted to call a dead actor"
|
20
|
+
end
|
21
|
+
|
16
22
|
if block_given?
|
17
23
|
# FIXME: nicer exception
|
18
24
|
raise "Cannot use blocks with futures yet"
|
@@ -21,11 +27,7 @@ module Celluloid
|
|
21
27
|
future = Future.new
|
22
28
|
call = SyncCall.new(future, meth, args, block)
|
23
29
|
|
24
|
-
|
25
|
-
@mailbox << call
|
26
|
-
rescue MailboxError
|
27
|
-
raise DeadActorError, "attempted to call a dead actor"
|
28
|
-
end
|
30
|
+
@mailbox << call
|
29
31
|
|
30
32
|
future
|
31
33
|
end
|
@@ -3,6 +3,9 @@ module Celluloid
|
|
3
3
|
class SyncProxy < AbstractProxy
|
4
4
|
attr_reader :mailbox
|
5
5
|
|
6
|
+
# Used for reflecting on proxy objects themselves
|
7
|
+
def __class__; SyncProxy; end
|
8
|
+
|
6
9
|
def initialize(mailbox, klass)
|
7
10
|
@mailbox, @klass = mailbox, klass
|
8
11
|
end
|
@@ -11,20 +14,22 @@ module Celluloid
|
|
11
14
|
"#<Celluloid::SyncProxy(#{@klass})>"
|
12
15
|
end
|
13
16
|
|
17
|
+
def respond_to?(meth, include_private = false)
|
18
|
+
__class__.instance_methods.include?(meth) || super
|
19
|
+
end
|
20
|
+
|
14
21
|
def method_missing(meth, *args, &block)
|
22
|
+
unless @mailbox.alive?
|
23
|
+
raise DeadActorError, "attempted to call a dead actor"
|
24
|
+
end
|
25
|
+
|
15
26
|
if @mailbox == ::Thread.current[:celluloid_mailbox]
|
16
27
|
args.unshift meth
|
17
28
|
meth = :__send__
|
18
29
|
end
|
19
30
|
|
20
31
|
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
|
-
|
32
|
+
@mailbox << call
|
28
33
|
call.value
|
29
34
|
end
|
30
35
|
end
|
data/lib/celluloid/rspec.rb
CHANGED
data/lib/celluloid/signals.rb
CHANGED
@@ -1,51 +1,23 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Event signaling between methods of the same object
|
3
3
|
class Signals
|
4
|
-
attr_reader :waiting
|
5
|
-
|
6
4
|
def initialize
|
7
|
-
@
|
5
|
+
@conditions = {}
|
8
6
|
end
|
9
7
|
|
10
8
|
# Wait for the given signal and return the associated value
|
11
|
-
def wait(
|
9
|
+
def wait(name)
|
12
10
|
raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
when Array
|
17
|
-
tasks << Task.current
|
18
|
-
when NilClass
|
19
|
-
@waiting[signal] = Task.current
|
20
|
-
else
|
21
|
-
@waiting[signal] = [tasks, Task.current]
|
22
|
-
end
|
23
|
-
|
24
|
-
Task.suspend :sigwait
|
12
|
+
@conditions[name] ||= Condition.new
|
13
|
+
@conditions[name].wait
|
25
14
|
end
|
26
15
|
|
27
16
|
# Send a signal to all method calls waiting for the given name
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
case tasks
|
33
|
-
when Array
|
34
|
-
tasks.each { |task| run_task task, value }
|
35
|
-
true if tasks.size > 0
|
36
|
-
when NilClass
|
37
|
-
false
|
38
|
-
else
|
39
|
-
run_task tasks, value
|
40
|
-
true
|
17
|
+
def broadcast(name, value = nil)
|
18
|
+
if condition = @conditions.delete(name)
|
19
|
+
condition.broadcast(value)
|
41
20
|
end
|
42
21
|
end
|
43
|
-
|
44
|
-
# Run the given task, reporting errors that occur
|
45
|
-
def run_task(task, value)
|
46
|
-
task.resume(value)
|
47
|
-
rescue => ex
|
48
|
-
Logger.crash("signaling error", ex)
|
49
|
-
end
|
50
22
|
end
|
51
23
|
end
|
data/lib/celluloid/stack_dump.rb
CHANGED
@@ -1,16 +1,60 @@
|
|
1
1
|
module Celluloid
|
2
2
|
class StackDump
|
3
|
+
module DisplayBacktrace
|
4
|
+
def display_backtrace(backtrace, output, indent = nil)
|
5
|
+
backtrace ||= ["EMPTY BACKTRACE"]
|
6
|
+
backtrace.each do |line|
|
7
|
+
output << indent if indent
|
8
|
+
output << "\t" << line << "\n"
|
9
|
+
end
|
10
|
+
output << "\n\n"
|
11
|
+
end
|
12
|
+
end
|
3
13
|
|
4
|
-
class TaskState < Struct.new(:task_class, :status, :backtrace)
|
14
|
+
class TaskState < Struct.new(:task_class, :type, :meta, :status, :backtrace)
|
5
15
|
end
|
6
16
|
|
7
17
|
class ActorState
|
18
|
+
include DisplayBacktrace
|
19
|
+
|
8
20
|
attr_accessor :subject_id, :subject_class, :name
|
9
21
|
attr_accessor :status, :tasks
|
10
22
|
attr_accessor :backtrace
|
23
|
+
|
24
|
+
def dump
|
25
|
+
string = ""
|
26
|
+
string << "Celluloid::Actor 0x#{subject_id.to_s(16)}: #{subject_class}"
|
27
|
+
string << " [#{name}]" if name
|
28
|
+
string << "\n"
|
29
|
+
|
30
|
+
if status == :idle
|
31
|
+
string << "State: Idle (waiting for messages)\n"
|
32
|
+
display_backtrace backtrace, string
|
33
|
+
else
|
34
|
+
string << "State: Running (executing tasks)\n"
|
35
|
+
display_backtrace backtrace, string
|
36
|
+
string << "\tTasks:\n"
|
37
|
+
|
38
|
+
tasks.each_with_index do |task, i|
|
39
|
+
string << "\t #{i+1}) #{task.task_class}[#{task.type}]: #{task.status}\n"
|
40
|
+
string << "\t #{task.meta.inspect}\n"
|
41
|
+
display_backtrace task.backtrace, string, "\t"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
string
|
46
|
+
end
|
11
47
|
end
|
12
48
|
|
13
49
|
class ThreadState < Struct.new(:thread_id, :backtrace)
|
50
|
+
include DisplayBacktrace
|
51
|
+
|
52
|
+
def dump
|
53
|
+
string = ""
|
54
|
+
string << "Thread 0x#{thread_id.to_s(16)}:\n"
|
55
|
+
display_backtrace backtrace, string
|
56
|
+
string
|
57
|
+
end
|
14
58
|
end
|
15
59
|
|
16
60
|
attr_accessor :actors, :threads
|
@@ -23,9 +67,8 @@ module Celluloid
|
|
23
67
|
end
|
24
68
|
|
25
69
|
def snapshot
|
26
|
-
|
27
|
-
if thread.
|
28
|
-
next unless thread.role == :actor
|
70
|
+
Celluloid.internal_pool.each do |thread|
|
71
|
+
if thread.role == :actor
|
29
72
|
@actors << snapshot_actor(thread.actor) if thread.actor
|
30
73
|
else
|
31
74
|
@threads << snapshot_thread(thread)
|
@@ -43,7 +86,7 @@ module Celluloid
|
|
43
86
|
state.status = :idle
|
44
87
|
else
|
45
88
|
state.status = :running
|
46
|
-
state.tasks = tasks.
|
89
|
+
state.tasks = tasks.to_a.map { |t| TaskState.new(t.class, t.type, t.meta, t.status, t.backtrace) }
|
47
90
|
end
|
48
91
|
|
49
92
|
state.backtrace = actor.thread.backtrace if actor.thread
|
@@ -56,41 +99,11 @@ module Celluloid
|
|
56
99
|
|
57
100
|
def dump(output = STDERR)
|
58
101
|
@actors.each do |actor|
|
59
|
-
|
60
|
-
string << "Celluloid::Actor 0x#{actor.subject_id.to_s(16)}: #{actor.subject_class}"
|
61
|
-
string << " [#{actor.name}]" if actor.name
|
62
|
-
string << "\n"
|
63
|
-
|
64
|
-
if actor.status == :idle
|
65
|
-
string << "State: Idle (waiting for messages)\n"
|
66
|
-
display_backtrace actor.backtrace, string
|
67
|
-
else
|
68
|
-
string << "State: Running (executing tasks)\n"
|
69
|
-
display_backtrace actor.backtrace, string
|
70
|
-
string << "Tasks:\n"
|
71
|
-
|
72
|
-
actor.tasks.each_with_index do |task, i|
|
73
|
-
string << " #{i+1}) #{task.task_class}: #{task.status}\n"
|
74
|
-
display_backtrace task.backtrace, string
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
output.print string
|
102
|
+
output.print actor.dump
|
79
103
|
end
|
80
104
|
|
81
105
|
@threads.each do |thread|
|
82
|
-
|
83
|
-
string << "Thread 0x#{thread.thread_id.to_s(16)}:\n"
|
84
|
-
display_backtrace thread.backtrace, string
|
85
|
-
output.print string
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def display_backtrace(backtrace, output)
|
90
|
-
if backtrace
|
91
|
-
output << "\t" << backtrace.join("\n\t") << "\n\n"
|
92
|
-
else
|
93
|
-
output << "EMPTY BACKTRACE\n\n"
|
106
|
+
output.print thread.dump
|
94
107
|
end
|
95
108
|
end
|
96
109
|
end
|
@@ -11,8 +11,8 @@ module Celluloid
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Start this application (and watch it with a supervisor)
|
14
|
-
def run!
|
15
|
-
group = new do |_group|
|
14
|
+
def run!(registry = nil)
|
15
|
+
group = new(registry) do |_group|
|
16
16
|
blocks.each do |block|
|
17
17
|
block.call(_group)
|
18
18
|
end
|
@@ -21,9 +21,9 @@ module Celluloid
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Run the application in the foreground with a simple watchdog
|
24
|
-
def run
|
24
|
+
def run(registry = nil)
|
25
25
|
loop do
|
26
|
-
supervisor = run!
|
26
|
+
supervisor = run!(registry)
|
27
27
|
|
28
28
|
# Take five, toplevel supervisor
|
29
29
|
sleep 5 while supervisor.alive?
|
@@ -149,7 +149,7 @@ module Celluloid
|
|
149
149
|
|
150
150
|
def terminate
|
151
151
|
@actor.terminate if @actor
|
152
|
-
rescue DeadActorError
|
152
|
+
rescue DeadActorError
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Celluloid
|
5
|
+
if defined? JRUBY_VERSION
|
6
|
+
require 'jruby/synchronized'
|
7
|
+
|
8
|
+
class TaskSet
|
9
|
+
extend Forwardable
|
10
|
+
include JRuby::Synchronized
|
11
|
+
|
12
|
+
def_delegators :@tasks, :<<, :delete, :first, :empty?, :to_a
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@tasks = Set.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
elsif defined? Rubinius
|
19
|
+
class TaskSet
|
20
|
+
def initialize
|
21
|
+
@tasks = Set.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(task)
|
25
|
+
Rubinius.synchronize(self) { @tasks << task }
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(task)
|
29
|
+
Rubinius.synchronize(self) { @tasks.delete task }
|
30
|
+
end
|
31
|
+
|
32
|
+
def first
|
33
|
+
Rubinius.synchronize(self) { @tasks.first }
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty?
|
37
|
+
Rubinius.synchronize(self) { @tasks.empty? }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
Rubinius.synchronize(self) { @tasks.to_a }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
# Assume we're on MRI, where we have the GIL. But what about IronRuby?
|
46
|
+
# Or MacRuby. Do people care? This will break Celluloid::StackDumps
|
47
|
+
TaskSet = Set
|
48
|
+
end
|
49
|
+
end
|