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/boot.rb
CHANGED
@@ -8,3 +8,16 @@ Celluloid.logger = Logger.new(STDERR)
|
|
8
8
|
# FIXME: We should set up the supervision hierarchy here
|
9
9
|
Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
|
10
10
|
Celluloid::IncidentReporter.supervise_as :default_incident_reporter, STDERR
|
11
|
+
|
12
|
+
# Terminate all actors at exit
|
13
|
+
at_exit do
|
14
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION >= "1.9"
|
15
|
+
# workaround for MRI bug losing exit status in at_exit block
|
16
|
+
# http://bugs.ruby-lang.org/issues/5218
|
17
|
+
exit_status = $!.status if $!.is_a?(SystemExit)
|
18
|
+
Celluloid.shutdown
|
19
|
+
exit exit_status if exit_status
|
20
|
+
else
|
21
|
+
Celluloid.shutdown
|
22
|
+
end
|
23
|
+
end
|
data/lib/celluloid/calls.rb
CHANGED
@@ -7,29 +7,69 @@ module Celluloid
|
|
7
7
|
@method, @arguments, @block = method, arguments, block
|
8
8
|
end
|
9
9
|
|
10
|
+
def dispatch(obj)
|
11
|
+
obj.public_send(@method, *@arguments, &@block)
|
12
|
+
rescue NoMethodError => ex
|
13
|
+
# Abort if the caller made a mistake
|
14
|
+
detect_missing_method(ex)
|
15
|
+
|
16
|
+
# Otherwise something blew up. Crash this actor
|
17
|
+
raise
|
18
|
+
rescue ArgumentError => ex
|
19
|
+
# Abort if the caller made a mistake
|
20
|
+
detect_argument_error(ex)
|
21
|
+
|
22
|
+
# Otherwise something blew up. Crash this actor
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Detect NoMethodErrors made by the caller and abort
|
29
|
+
def detect_missing_method(ex)
|
30
|
+
ex.backtrace.each do |frame|
|
31
|
+
break if frame["celluloid/lib/celluloid/calls.rb"] || frame["`public_send'"]
|
32
|
+
return unless frame["`method_missing'"]
|
33
|
+
end
|
34
|
+
|
35
|
+
raise AbortError.new(ex)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Detect ArgumentErrors made by the caller and abort
|
39
|
+
def detect_argument_error(ex)
|
40
|
+
if ex.backtrace[0]["`#{@method}'"] && ex.backtrace[1]["`public_send'"]
|
41
|
+
raise AbortError.new(ex)
|
42
|
+
end
|
43
|
+
end
|
10
44
|
end
|
11
45
|
|
12
46
|
# Synchronous calls wait for a response
|
13
47
|
class SyncCall < Call
|
14
|
-
attr_reader :caller, :task
|
48
|
+
attr_reader :caller, :task, :chain_id
|
15
49
|
|
16
|
-
def initialize(caller, method, arguments = [], block = nil, task = Thread.current[:
|
50
|
+
def initialize(caller, method, arguments = [], block = nil, task = Thread.current[:celluloid_task], chain_id = Thread.current[:celluloid_chain_id])
|
17
51
|
super(method, arguments, block)
|
18
|
-
|
19
|
-
@
|
52
|
+
|
53
|
+
@caller = caller
|
54
|
+
@task = task
|
55
|
+
@chain_id = chain_id || Celluloid.uuid
|
20
56
|
end
|
21
57
|
|
22
58
|
def dispatch(obj)
|
23
|
-
|
59
|
+
Thread.current[:celluloid_chain_id] = @chain_id
|
60
|
+
result = super(obj)
|
24
61
|
respond SuccessResponse.new(self, result)
|
25
62
|
rescue Exception => ex
|
26
63
|
# Exceptions that occur during synchronous calls are reraised in the
|
27
64
|
# context of the caller
|
28
65
|
respond ErrorResponse.new(self, ex)
|
66
|
+
|
29
67
|
# Aborting indicates a protocol error on the part of the caller
|
30
68
|
# It should crash the caller, but the exception isn't reraised
|
31
69
|
# Otherwise, it's a bug in this actor and should be reraised
|
32
|
-
ex.is_a?(AbortError)
|
70
|
+
raise unless ex.is_a?(AbortError)
|
71
|
+
ensure
|
72
|
+
Thread.current[:celluloid_chain_id] = nil
|
33
73
|
end
|
34
74
|
|
35
75
|
def cleanup
|
@@ -43,17 +83,19 @@ module Celluloid
|
|
43
83
|
# It's possible the caller exited or crashed before we could send a
|
44
84
|
# response to them.
|
45
85
|
end
|
46
|
-
|
47
86
|
end
|
48
87
|
|
49
88
|
# Asynchronous calls don't wait for a response
|
50
89
|
class AsyncCall < Call
|
51
90
|
|
52
91
|
def dispatch(obj)
|
53
|
-
|
92
|
+
Thread.current[:celluloid_chain_id] = Celluloid.uuid
|
93
|
+
super(obj)
|
54
94
|
rescue AbortError => ex
|
55
95
|
# Swallow aborted async calls, as they indicate the caller made a mistake
|
56
96
|
Logger.debug("#{obj.class}: async call `#@method` aborted!\n#{Logger.format_exception(ex.cause)}")
|
97
|
+
ensure
|
98
|
+
Thread.current[:celluloid_chain_id] = nil
|
57
99
|
end
|
58
100
|
|
59
101
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Celluloid
|
2
|
+
class ConditionError < StandardError; end
|
3
|
+
|
4
|
+
# ConditionVariable-like signaling between tasks and actors
|
5
|
+
class Condition
|
6
|
+
attr_reader :owner
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@owner = Actor.current
|
11
|
+
@tasks = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# Wait for the given signal and return the associated value
|
15
|
+
def wait
|
16
|
+
raise ConditionError, "cannot wait for signals while exclusive" if Celluloid.exclusive?
|
17
|
+
|
18
|
+
@mutex.synchronize do
|
19
|
+
raise ConditionError, "can't wait unless owner" unless Actor.current == @owner
|
20
|
+
@tasks << Task.current
|
21
|
+
end
|
22
|
+
|
23
|
+
result = Task.suspend :condwait
|
24
|
+
raise result if result.is_a? ConditionError
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
# Send a signal to the first task waiting on this condition
|
29
|
+
def signal(value = nil)
|
30
|
+
@mutex.synchronize do
|
31
|
+
if task = @tasks.shift
|
32
|
+
@owner.mailbox << SignalConditionRequest.new(task, value)
|
33
|
+
else
|
34
|
+
Logger.debug("Celluloid::Condition signaled spuriously")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Broadcast a value to all waiting tasks
|
40
|
+
def broadcast(value = nil)
|
41
|
+
@mutex.synchronize do
|
42
|
+
@tasks.each { |task| @owner.mailbox << SignalConditionRequest.new(task, value) }
|
43
|
+
@tasks.clear
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Change the owner of this condition
|
48
|
+
def owner=(actor)
|
49
|
+
@mutex.synchronize do
|
50
|
+
if @owner != actor
|
51
|
+
@tasks.each do |task|
|
52
|
+
ex = ConditionError.new("ownership changed")
|
53
|
+
@owner.mailbox << SignalConditionRequest.new(task, ex)
|
54
|
+
end
|
55
|
+
@tasks.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
@owner = actor
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :inspect, :to_s
|
63
|
+
end
|
64
|
+
end
|
data/lib/celluloid/core_ext.rb
CHANGED
@@ -6,7 +6,7 @@ class Thread
|
|
6
6
|
|
7
7
|
# Retrieve the mailbox for the current thread or lazily initialize it
|
8
8
|
def self.mailbox
|
9
|
-
current[:
|
9
|
+
current[:celluloid_mailbox] ||= Celluloid::Mailbox.new
|
10
10
|
end
|
11
11
|
|
12
12
|
# Receive a message either as an actor or through the local mailbox
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
module CPUCounter
|
5
|
+
case RbConfig::CONFIG['host_os'][/^[A-Za-z]+/]
|
6
|
+
when 'darwin'
|
7
|
+
@cores = Integer(`sysctl hw.ncpu`[/\d+/])
|
8
|
+
when 'linux'
|
9
|
+
@cores = File.read("/proc/cpuinfo").scan(/(?:core id|processor)\s+: \d+/).uniq.size
|
10
|
+
when 'mingw', 'mswin'
|
11
|
+
@cores = Integer(`SET NUMBER_OF_PROCESSORS`[/\d+/])
|
12
|
+
else
|
13
|
+
@cores = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cores; @cores; end
|
17
|
+
end
|
18
|
+
end
|
data/lib/celluloid/future.rb
CHANGED
@@ -4,8 +4,11 @@ module Celluloid
|
|
4
4
|
# Celluloid::Future objects allow methods and blocks to run in the
|
5
5
|
# background, their values requested later
|
6
6
|
class Future
|
7
|
+
attr_reader :address
|
8
|
+
|
7
9
|
# Create a future bound to a given receiver, or with a block to compute
|
8
10
|
def initialize(*args, &block)
|
11
|
+
@address = Celluloid.uuid
|
9
12
|
@mutex = Mutex.new
|
10
13
|
@ready = false
|
11
14
|
@result = nil
|
@@ -13,7 +16,7 @@ module Celluloid
|
|
13
16
|
|
14
17
|
if block
|
15
18
|
@call = SyncCall.new(self, :call, args)
|
16
|
-
|
19
|
+
Celluloid.internal_pool.get do
|
17
20
|
begin
|
18
21
|
@call.dispatch(block)
|
19
22
|
rescue
|
@@ -2,61 +2,79 @@ require 'thread'
|
|
2
2
|
|
3
3
|
module Celluloid
|
4
4
|
# Maintain a thread pool FOR SPEED!!
|
5
|
-
|
6
|
-
|
7
|
-
@mutex = Mutex.new
|
5
|
+
class InternalPool
|
6
|
+
attr_accessor :busy_size, :idle_size, :max_idle
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
def initialize
|
9
|
+
@pool = []
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@busy_size = @idle_size = 0
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# Get a thread from the pool, running the given block
|
16
|
-
def get(&block)
|
17
|
-
@mutex.synchronize do
|
18
|
-
begin
|
19
|
-
if @pool.empty?
|
20
|
-
thread = create
|
21
|
-
else
|
22
|
-
thread = @pool.shift
|
23
|
-
end
|
24
|
-
end until thread.status # handle crashed threads
|
25
|
-
|
26
|
-
thread[:queue] << block
|
27
|
-
thread
|
28
|
-
end
|
29
|
-
end
|
13
|
+
# TODO: should really adjust this based on usage
|
14
|
+
@max_idle = 16
|
15
|
+
end
|
30
16
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
# Get a thread from the pool, running the given block
|
18
|
+
def get(&block)
|
19
|
+
@mutex.synchronize do
|
20
|
+
begin
|
21
|
+
if @pool.empty?
|
22
|
+
thread = create
|
36
23
|
else
|
37
|
-
@pool
|
24
|
+
thread = @pool.shift
|
25
|
+
@idle_size -= 1
|
38
26
|
end
|
27
|
+
end until thread.status # handle crashed threads
|
28
|
+
|
29
|
+
@busy_size += 1
|
30
|
+
thread[:celluloid_queue] << block
|
31
|
+
thread
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return a thread to the pool
|
36
|
+
def put(thread)
|
37
|
+
@mutex.synchronize do
|
38
|
+
if @pool.size >= @max_idle
|
39
|
+
thread[:celluloid_queue] << nil
|
40
|
+
else
|
41
|
+
clean_thread_locals(thread)
|
42
|
+
@pool << thread
|
43
|
+
@idle_size += 1
|
44
|
+
@busy_size -= 1
|
39
45
|
end
|
40
46
|
end
|
47
|
+
end
|
41
48
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
put thread
|
49
|
+
# Create a new thread with an associated queue of procs to run
|
50
|
+
def create
|
51
|
+
queue = Queue.new
|
52
|
+
thread = Thread.new do
|
53
|
+
while proc = queue.pop
|
54
|
+
begin
|
55
|
+
proc.call
|
56
|
+
rescue => ex
|
57
|
+
Logger.crash("thread crashed", ex)
|
54
58
|
end
|
59
|
+
|
60
|
+
put thread
|
55
61
|
end
|
62
|
+
end
|
56
63
|
|
57
|
-
|
58
|
-
|
64
|
+
thread[:celluloid_queue] = queue
|
65
|
+
thread
|
66
|
+
end
|
67
|
+
|
68
|
+
# Clean the thread locals of an incoming thread
|
69
|
+
def clean_thread_locals(thread)
|
70
|
+
thread.keys.each do |key|
|
71
|
+
next if key == :celluloid_queue
|
72
|
+
|
73
|
+
# Ruby seems to lack an API for deleting thread locals. WTF, Ruby?
|
74
|
+
thread[key] = nil
|
59
75
|
end
|
60
76
|
end
|
61
77
|
end
|
62
|
-
|
78
|
+
|
79
|
+
self.internal_pool = InternalPool.new
|
80
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Celluloid
|
2
|
+
class ActorProxy
|
3
|
+
# method_missing black magic to call bang predicate methods asynchronously
|
4
|
+
def method_missing(meth, *args, &block)
|
5
|
+
# bang methods are async calls
|
6
|
+
if meth.match(/!$/)
|
7
|
+
Logger.deprecate("'bang method'-style async syntax is deprecated and will be removed in Celluloid 1.0." +
|
8
|
+
"Call async methods with 'actor.async.method'.")
|
9
|
+
|
10
|
+
unbanged_meth = meth.to_s
|
11
|
+
unbanged_meth.slice!(-1, 1)
|
12
|
+
Actor.async @mailbox, unbanged_meth, *args, &block
|
13
|
+
else
|
14
|
+
Actor.call @mailbox, meth, *args, &block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
# Process async calls via method_missing
|
21
|
+
def method_missing(meth, *args, &block)
|
22
|
+
# bang methods are async calls
|
23
|
+
if meth.to_s.match(/!$/)
|
24
|
+
Logger.deprecate("'bang method'-style async syntax is deprecated and will be removed in Celluloid 1.0." +
|
25
|
+
"Call async methods with 'actor.async.method'.")
|
26
|
+
|
27
|
+
unbanged_meth = meth.to_s.sub(/!$/, '')
|
28
|
+
args.unshift unbanged_meth
|
29
|
+
|
30
|
+
call = AsyncCall.new(:__send__, args, block)
|
31
|
+
begin
|
32
|
+
Thread.current[:celluloid_actor].mailbox << call
|
33
|
+
rescue MailboxError
|
34
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
35
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
36
|
+
# async calls should find other ways to deal with actors dying
|
37
|
+
# during an async call (i.e. linking/supervisors)
|
38
|
+
end
|
39
|
+
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/celluloid/links.rb
CHANGED
@@ -27,11 +27,6 @@ module Celluloid
|
|
27
27
|
@links.each { |_, actor| yield(actor) }
|
28
28
|
end
|
29
29
|
|
30
|
-
# Send an event message to all actors
|
31
|
-
def send_event(event)
|
32
|
-
each { |actor| actor.mailbox << event }
|
33
|
-
end
|
34
|
-
|
35
30
|
# Generate a string representation
|
36
31
|
def inspect
|
37
32
|
links = self.map(&:inspect).join(',')
|
data/lib/celluloid/logger.rb
CHANGED
@@ -37,10 +37,10 @@ module Celluloid
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
#
|
41
|
-
def
|
42
|
-
|
43
|
-
|
40
|
+
# Note a deprecation
|
41
|
+
def deprecate(message)
|
42
|
+
trace = caller.join("\n\t")
|
43
|
+
warn "DEPRECATION WARNING: #{message}\n\t#{trace}"
|
44
44
|
end
|
45
45
|
|
46
46
|
# Define an exception handler
|
@@ -49,5 +49,11 @@ module Celluloid
|
|
49
49
|
@exception_handlers << block
|
50
50
|
nil
|
51
51
|
end
|
52
|
+
|
53
|
+
# Format an exception message
|
54
|
+
def format_exception(exception)
|
55
|
+
str = "#{exception.class}: #{exception.to_s}\n\t"
|
56
|
+
str << exception.backtrace.join("\n\t")
|
57
|
+
end
|
52
58
|
end
|
53
59
|
end
|
data/lib/celluloid/mailbox.rb
CHANGED
@@ -10,9 +10,10 @@ module Celluloid
|
|
10
10
|
include Enumerable
|
11
11
|
|
12
12
|
# A unique address at which this mailbox can be found
|
13
|
-
|
13
|
+
attr_reader :address
|
14
14
|
|
15
15
|
def initialize
|
16
|
+
@address = Celluloid.uuid
|
16
17
|
@messages = []
|
17
18
|
@mutex = Mutex.new
|
18
19
|
@dead = false
|