celluloid 0.12.4 → 0.13.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.
- 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
|