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