celluloid 0.15.2 → 0.16.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/LICENSE.txt +20 -0
- data/README.md +29 -2
- data/lib/celluloid.rb +68 -73
- data/lib/celluloid/actor.rb +69 -123
- data/lib/celluloid/actor_system.rb +107 -0
- data/lib/celluloid/calls.rb +16 -16
- data/lib/celluloid/cell.rb +89 -0
- data/lib/celluloid/condition.rb +25 -8
- data/lib/celluloid/cpu_counter.rb +2 -0
- data/lib/celluloid/evented_mailbox.rb +2 -1
- data/lib/celluloid/exceptions.rb +23 -0
- data/lib/celluloid/future.rb +1 -1
- data/lib/celluloid/handlers.rb +41 -0
- data/lib/celluloid/internal_pool.rb +0 -3
- data/lib/celluloid/logger.rb +30 -0
- data/lib/celluloid/logging/incident_logger.rb +1 -1
- data/lib/celluloid/mailbox.rb +19 -18
- data/lib/celluloid/method.rb +8 -0
- data/lib/celluloid/pool_manager.rb +1 -1
- data/lib/celluloid/probe.rb +73 -0
- data/lib/celluloid/properties.rb +2 -2
- data/lib/celluloid/proxies/actor_proxy.rb +9 -41
- data/lib/celluloid/proxies/cell_proxy.rb +68 -0
- data/lib/celluloid/proxies/sync_proxy.rb +1 -1
- data/lib/celluloid/receivers.rb +1 -0
- data/lib/celluloid/registry.rb +1 -8
- data/lib/celluloid/stack_dump.rb +34 -11
- data/lib/celluloid/supervision_group.rb +26 -14
- data/lib/celluloid/tasks.rb +6 -9
- data/lib/celluloid/tasks/task_fiber.rb +6 -0
- data/lib/celluloid/tasks/task_thread.rb +2 -1
- data/lib/celluloid/thread_handle.rb +2 -2
- data/spec/celluloid/actor_spec.rb +1 -1
- data/spec/celluloid/actor_system_spec.rb +69 -0
- data/spec/celluloid/block_spec.rb +1 -1
- data/spec/celluloid/calls_spec.rb +1 -1
- data/spec/celluloid/condition_spec.rb +14 -3
- data/spec/celluloid/cpu_counter_spec.rb +9 -0
- data/spec/celluloid/fsm_spec.rb +1 -1
- data/spec/celluloid/future_spec.rb +1 -1
- data/spec/celluloid/notifications_spec.rb +1 -1
- data/spec/celluloid/pool_spec.rb +1 -1
- data/spec/celluloid/probe_spec.rb +121 -0
- data/spec/celluloid/registry_spec.rb +6 -6
- data/spec/celluloid/stack_dump_spec.rb +37 -8
- data/spec/celluloid/supervision_group_spec.rb +7 -1
- data/spec/celluloid/supervisor_spec.rb +12 -1
- data/spec/celluloid/tasks/task_fiber_spec.rb +1 -1
- data/spec/celluloid/tasks/task_thread_spec.rb +1 -1
- data/spec/celluloid/thread_handle_spec.rb +7 -3
- data/spec/spec_helper.rb +20 -7
- data/spec/support/actor_examples.rb +33 -15
- data/spec/support/mailbox_examples.rb +9 -3
- data/spec/support/task_examples.rb +2 -0
- metadata +32 -22
@@ -0,0 +1,107 @@
|
|
1
|
+
module Celluloid
|
2
|
+
class ActorSystem
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@internal_pool = InternalPool.new
|
7
|
+
@registry = Registry.new
|
8
|
+
end
|
9
|
+
attr_reader :registry
|
10
|
+
|
11
|
+
# Launch default services
|
12
|
+
# FIXME: We should set up the supervision hierarchy here
|
13
|
+
def start
|
14
|
+
within do
|
15
|
+
Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
|
16
|
+
Celluloid::IncidentReporter.supervise_as :default_incident_reporter, STDERR
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def within
|
22
|
+
old = Thread.current[:celluloid_actor_system]
|
23
|
+
Thread.current[:celluloid_actor_system] = self
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
Thread.current[:celluloid_actor_system] = old
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_thread
|
30
|
+
@internal_pool.get do
|
31
|
+
Thread.current[:celluloid_actor_system] = self
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stack_dump
|
37
|
+
Celluloid::StackDump.new(@internal_pool)
|
38
|
+
end
|
39
|
+
|
40
|
+
def_delegators "@registry", :[], :get, :[]=, :set, :delete
|
41
|
+
|
42
|
+
def registered
|
43
|
+
@registry.names
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_registry
|
47
|
+
@registry.clear
|
48
|
+
end
|
49
|
+
|
50
|
+
def running
|
51
|
+
actors = []
|
52
|
+
@internal_pool.each do |t|
|
53
|
+
next unless t.role == :actor
|
54
|
+
actors << t.actor.behavior_proxy if t.actor && t.actor.respond_to?(:behavior_proxy)
|
55
|
+
end
|
56
|
+
actors
|
57
|
+
end
|
58
|
+
|
59
|
+
def running?
|
60
|
+
@internal_pool.running?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Shut down all running actors
|
64
|
+
def shutdown
|
65
|
+
actors = running
|
66
|
+
Timeout.timeout(shutdown_timeout) do
|
67
|
+
Logger.debug "Terminating #{actors.size} #{(actors.size > 1) ? 'actors' : 'actor'}..." if actors.size > 0
|
68
|
+
|
69
|
+
# Actors cannot self-terminate, you must do it for them
|
70
|
+
actors.each do |actor|
|
71
|
+
begin
|
72
|
+
actor.terminate!
|
73
|
+
rescue DeadActorError
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
actors.each do |actor|
|
78
|
+
begin
|
79
|
+
Actor.join(actor)
|
80
|
+
rescue DeadActorError
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@internal_pool.shutdown
|
85
|
+
end
|
86
|
+
rescue Timeout::Error
|
87
|
+
Logger.error("Couldn't cleanly terminate all actors in #{shutdown_timeout} seconds!")
|
88
|
+
actors.each do |actor|
|
89
|
+
begin
|
90
|
+
Actor.kill(actor)
|
91
|
+
rescue DeadActorError, MailboxDead
|
92
|
+
end
|
93
|
+
end
|
94
|
+
ensure
|
95
|
+
@internal_pool.kill
|
96
|
+
clear_registry
|
97
|
+
end
|
98
|
+
|
99
|
+
def assert_inactive
|
100
|
+
@internal_pool.assert_inactive
|
101
|
+
end
|
102
|
+
|
103
|
+
def shutdown_timeout
|
104
|
+
Celluloid.shutdown_timeout
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/celluloid/calls.rb
CHANGED
@@ -21,32 +21,28 @@ module Celluloid
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def dispatch(obj)
|
24
|
+
check(obj)
|
24
25
|
_block = @block && @block.to_proc
|
25
26
|
obj.public_send(@method, *@arguments, &_block)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
raise
|
32
|
-
rescue ArgumentError => ex
|
33
|
-
# Abort if the sender made a mistake
|
27
|
+
end
|
28
|
+
|
29
|
+
def check(obj)
|
30
|
+
raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}" unless obj.respond_to? @method
|
31
|
+
|
34
32
|
begin
|
35
33
|
arity = obj.method(@method).arity
|
36
34
|
rescue NameError
|
37
|
-
|
38
|
-
raise AbortError.new(ex)
|
35
|
+
return
|
39
36
|
end
|
40
37
|
|
41
38
|
if arity >= 0
|
42
|
-
raise
|
39
|
+
raise ArgumentError, "wrong number of arguments (#{@arguments.size} for #{arity})" if @arguments.size != arity
|
43
40
|
elsif arity < -1
|
44
41
|
mandatory_args = -arity - 1
|
45
|
-
raise
|
42
|
+
raise ArgumentError, "wrong number of arguments (#{@arguments.size} for #{mandatory_args}+)" if arguments.size < mandatory_args
|
46
43
|
end
|
47
|
-
|
48
|
-
|
49
|
-
raise
|
44
|
+
rescue => ex
|
45
|
+
raise AbortError.new(ex)
|
50
46
|
end
|
51
47
|
end
|
52
48
|
|
@@ -88,8 +84,12 @@ module Celluloid
|
|
88
84
|
@sender << message
|
89
85
|
end
|
90
86
|
|
87
|
+
def response
|
88
|
+
Celluloid.suspend(:callwait, self)
|
89
|
+
end
|
90
|
+
|
91
91
|
def value
|
92
|
-
|
92
|
+
response.value
|
93
93
|
end
|
94
94
|
|
95
95
|
def wait
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Celluloid
|
2
|
+
OWNER_IVAR = :@celluloid_owner # reference to owning actor
|
3
|
+
|
4
|
+
# Wrap the given subject with an Cell
|
5
|
+
class Cell
|
6
|
+
class ExitHandler
|
7
|
+
def initialize(behavior, subject, method_name)
|
8
|
+
@behavior = behavior
|
9
|
+
@subject = subject
|
10
|
+
@method_name = method_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(event)
|
14
|
+
@behavior.task(:exit_handler, @method_name) do
|
15
|
+
@subject.send(@method_name, event.actor, event.reason)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(subject, options, actor_options)
|
21
|
+
@actor = Actor.new(self, actor_options)
|
22
|
+
@subject = subject
|
23
|
+
@receiver_block_executions = options[:receiver_block_executions]
|
24
|
+
@exclusive_methods = options[:exclusive_methods]
|
25
|
+
@finalizer = options[:finalizer]
|
26
|
+
|
27
|
+
@subject.instance_variable_set(OWNER_IVAR, @actor)
|
28
|
+
|
29
|
+
if exit_handler_name = options[:exit_handler_name]
|
30
|
+
@actor.exit_handler = ExitHandler.new(self, @subject, exit_handler_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
@actor.handle(Call) do |message|
|
34
|
+
invoke(message)
|
35
|
+
end
|
36
|
+
@actor.handle(BlockCall) do |message|
|
37
|
+
task(:invoke_block) { message.dispatch }
|
38
|
+
end
|
39
|
+
@actor.handle(BlockResponse, Response) do |message|
|
40
|
+
message.dispatch
|
41
|
+
end
|
42
|
+
|
43
|
+
@actor.start
|
44
|
+
@proxy = (options[:proxy_class] || CellProxy).new(@actor.proxy, @actor.mailbox, @subject.class.to_s)
|
45
|
+
end
|
46
|
+
attr_reader :proxy, :subject
|
47
|
+
|
48
|
+
def invoke(call)
|
49
|
+
meth = call.method
|
50
|
+
if meth == :__send__
|
51
|
+
meth = call.arguments.first
|
52
|
+
end
|
53
|
+
if @receiver_block_executions && meth
|
54
|
+
if @receiver_block_executions.include?(meth.to_sym)
|
55
|
+
call.execute_block_on_receiver
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
task(:call, meth, :dangerous_suspend => meth == :initialize) {
|
60
|
+
call.dispatch(@subject)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def task(task_type, method_name = nil, meta = nil, &block)
|
65
|
+
meta ||= {}
|
66
|
+
meta.merge!(:method_name => method_name)
|
67
|
+
@actor.task(task_type, meta) do
|
68
|
+
if @exclusive_methods && method_name && @exclusive_methods.include?(method_name.to_sym)
|
69
|
+
Celluloid.exclusive { yield }
|
70
|
+
else
|
71
|
+
yield
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Run the user-defined finalizer, if one is set
|
77
|
+
def shutdown
|
78
|
+
return unless @finalizer && @subject.respond_to?(@finalizer, true)
|
79
|
+
|
80
|
+
task(:finalizer, @finalizer, :dangerous_suspend => true) do
|
81
|
+
begin
|
82
|
+
@subject.__send__(@finalizer)
|
83
|
+
rescue => ex
|
84
|
+
Logger.crash("#{@subject.class} finalizer crashed!", ex)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/celluloid/condition.rb
CHANGED
@@ -4,10 +4,11 @@ module Celluloid
|
|
4
4
|
# ConditionVariable-like signaling between tasks and threads
|
5
5
|
class Condition
|
6
6
|
class Waiter
|
7
|
-
def initialize(condition, task, mailbox)
|
7
|
+
def initialize(condition, task, mailbox, timeout)
|
8
8
|
@condition = condition
|
9
9
|
@task = task
|
10
10
|
@mailbox = mailbox
|
11
|
+
@timeout = timeout
|
11
12
|
end
|
12
13
|
attr_reader :condition, :task
|
13
14
|
|
@@ -16,9 +17,14 @@ module Celluloid
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def wait
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
begin
|
21
|
+
message = @mailbox.receive(@timeout) do |msg|
|
22
|
+
msg.is_a?(SignalConditionRequest) && msg.task == Thread.current
|
23
|
+
end
|
24
|
+
rescue TimeoutError
|
25
|
+
raise ConditionError, "timeout after #{@timeout.inspect} seconds"
|
26
|
+
end until message
|
27
|
+
|
22
28
|
message.value
|
23
29
|
end
|
24
30
|
end
|
@@ -29,21 +35,30 @@ module Celluloid
|
|
29
35
|
end
|
30
36
|
|
31
37
|
# Wait for the given signal and return the associated value
|
32
|
-
def wait
|
38
|
+
def wait(timeout = nil)
|
33
39
|
raise ConditionError, "cannot wait for signals while exclusive" if Celluloid.exclusive?
|
34
40
|
|
35
|
-
if Thread.current[:celluloid_actor]
|
41
|
+
if actor = Thread.current[:celluloid_actor]
|
36
42
|
task = Task.current
|
43
|
+
if timeout
|
44
|
+
bt = caller
|
45
|
+
timer = actor.timers.after(timeout) do
|
46
|
+
exception = ConditionError.new("timeout after #{timeout.inspect} seconds")
|
47
|
+
exception.set_backtrace bt
|
48
|
+
task.resume exception
|
49
|
+
end
|
50
|
+
end
|
37
51
|
else
|
38
52
|
task = Thread.current
|
39
53
|
end
|
40
|
-
waiter = Waiter.new(self, task, Celluloid.mailbox)
|
54
|
+
waiter = Waiter.new(self, task, Celluloid.mailbox, timeout)
|
41
55
|
|
42
56
|
@mutex.synchronize do
|
43
57
|
@waiters << waiter
|
44
58
|
end
|
45
59
|
|
46
60
|
result = Celluloid.suspend :condwait, waiter
|
61
|
+
timer.cancel if timer
|
47
62
|
raise result if result.is_a? ConditionError
|
48
63
|
result
|
49
64
|
end
|
@@ -54,7 +69,9 @@ module Celluloid
|
|
54
69
|
if waiter = @waiters.shift
|
55
70
|
waiter << SignalConditionRequest.new(waiter.task, value)
|
56
71
|
else
|
57
|
-
Logger.
|
72
|
+
Logger.with_backtrace(caller(3)) do |logger|
|
73
|
+
logger.debug("Celluloid::Condition signaled spuriously")
|
74
|
+
end
|
58
75
|
end
|
59
76
|
end
|
60
77
|
end
|
@@ -42,10 +42,11 @@ module Celluloid
|
|
42
42
|
|
43
43
|
until message
|
44
44
|
if timeout
|
45
|
+
# TODO: use hitimes/timers instead of Time.now
|
45
46
|
now = Time.now
|
46
47
|
wait_until ||= now + timeout
|
47
48
|
wait_interval = wait_until - now
|
48
|
-
|
49
|
+
raise(TimeoutError, "mailbox timeout exceeded", nil) if wait_interval <= 0
|
49
50
|
else
|
50
51
|
wait_interval = nil
|
51
52
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Base class of all Celluloid errors
|
3
|
+
Error = Class.new(StandardError)
|
4
|
+
|
5
|
+
# Don't do Actor-like things outside Actor scope
|
6
|
+
NotActorError = Class.new(Celluloid::Error)
|
7
|
+
|
8
|
+
# Trying to do something to a dead actor
|
9
|
+
DeadActorError = Class.new(Celluloid::Error)
|
10
|
+
|
11
|
+
# A timeout occured before the given request could complete
|
12
|
+
TimeoutError = Class.new(Celluloid::Error)
|
13
|
+
|
14
|
+
# The sender made an error, not the current actor
|
15
|
+
class AbortError < Celluloid::Error
|
16
|
+
attr_reader :cause
|
17
|
+
|
18
|
+
def initialize(cause)
|
19
|
+
@cause = cause
|
20
|
+
super "caused by #{cause.inspect}: #{cause.to_s}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/celluloid/future.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
class Handlers
|
5
|
+
def initialize
|
6
|
+
@handlers = Set.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle(*patterns, &block)
|
10
|
+
patterns.each do |pattern|
|
11
|
+
handler = Handler.new pattern, block
|
12
|
+
@handlers << handler
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Handle incoming messages
|
17
|
+
def handle_message(message)
|
18
|
+
if handler = @handlers.find { |h| h.match(message) }
|
19
|
+
handler.call message
|
20
|
+
handler
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Methods blocking on a call to receive
|
26
|
+
class Handler
|
27
|
+
def initialize(pattern, block)
|
28
|
+
@pattern = pattern
|
29
|
+
@block = block
|
30
|
+
end
|
31
|
+
|
32
|
+
# Match a message with this receiver's block
|
33
|
+
def match(message)
|
34
|
+
@pattern === message
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(message)
|
38
|
+
@block.call message
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -6,7 +6,6 @@ module Celluloid
|
|
6
6
|
attr_accessor :max_idle
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
@group = ThreadGroup.new
|
10
9
|
@mutex = Mutex.new
|
11
10
|
@threads = []
|
12
11
|
|
@@ -108,7 +107,6 @@ module Celluloid
|
|
108
107
|
|
109
108
|
thread[:celluloid_queue] = queue
|
110
109
|
@threads << thread
|
111
|
-
@group.add(thread)
|
112
110
|
thread
|
113
111
|
end
|
114
112
|
|
@@ -137,7 +135,6 @@ module Celluloid
|
|
137
135
|
@running = false
|
138
136
|
|
139
137
|
@threads.shift.kill until @threads.empty?
|
140
|
-
@group.list.each(&:kill)
|
141
138
|
end
|
142
139
|
end
|
143
140
|
|