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.
@@ -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
@@ -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[:task])
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
- @caller = caller
19
- @task = task
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
- result = obj.public_send(@method, *@arguments, &@block)
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) ? nil : raise
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
- obj.public_send(@method, *@arguments, &@block)
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
@@ -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[:mailbox] ||= Celluloid::Mailbox.new
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
@@ -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
- InternalPool.get do
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
- module InternalPool
6
- @pool = []
7
- @mutex = Mutex.new
5
+ class InternalPool
6
+ attr_accessor :busy_size, :idle_size, :max_idle
8
7
 
9
- # TODO: should really adjust this based on usage
10
- @max_idle = 16
8
+ def initialize
9
+ @pool = []
10
+ @mutex = Mutex.new
11
+ @busy_size = @idle_size = 0
11
12
 
12
- class << self
13
- attr_accessor :max_idle
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
- # Return a thread to the pool
32
- def put(thread)
33
- @mutex.synchronize do
34
- if @pool.size >= @max_idle
35
- thread[:queue] << nil
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 << thread
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
- # Create a new thread with an associated queue of procs to run
43
- def create
44
- queue = Queue.new
45
- thread = Thread.new do
46
- while proc = queue.pop
47
- begin
48
- proc.call
49
- rescue => ex
50
- Logger.crash("thread crashed", ex)
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
- thread[:queue] = queue
58
- thread
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
- end
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
@@ -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(',')
@@ -37,10 +37,10 @@ module Celluloid
37
37
  end
38
38
  end
39
39
 
40
- # Format an exception message
41
- def format_exception(exception)
42
- str = "#{exception.class}: #{exception.to_s}\n"
43
- str << exception.backtrace.join("\n")
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
@@ -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
- alias_method :address, :object_id
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