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.
@@ -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