celluloid 0.11.1 → 0.12.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,8 +2,8 @@ module Celluloid
2
2
  # A proxy object returned from Celluloid::Actor.spawn/spawn_link which
3
3
  # dispatches calls and casts to normal Ruby objects which are running inside
4
4
  # of their own threads.
5
- class ActorProxy
6
- attr_reader :mailbox
5
+ class ActorProxy < BasicObject
6
+ attr_reader :mailbox, :thread
7
7
 
8
8
  def initialize(actor)
9
9
  @mailbox, @thread, @klass = actor.mailbox, actor.thread, actor.subject.class.to_s
@@ -13,6 +13,19 @@ module Celluloid
13
13
  Actor.call @mailbox, :__send__, meth, *args, &block
14
14
  end
15
15
 
16
+ # Needed for storing proxies in data structures
17
+ needed = [:object_id, :__id__, :hash] - instance_methods
18
+ if needed.any?
19
+ include ::Kernel.dup.module_eval {
20
+ undef_method *(instance_methods - needed)
21
+ self
22
+ }
23
+
24
+ # rubinius bug? These methods disappear when we include hacked kernel
25
+ define_method :==, ::BasicObject.instance_method(:==) unless instance_methods.include?(:==)
26
+ alias_method(:equal?, :==) unless instance_methods.include?(:equal?)
27
+ end
28
+
16
29
  def class
17
30
  Actor.call @mailbox, :__send__, :class
18
31
  end
@@ -29,14 +42,18 @@ module Celluloid
29
42
  Actor.call @mailbox, :kind_of?, klass
30
43
  end
31
44
 
32
- def respond_to?(meth)
33
- Actor.call @mailbox, :respond_to?, meth
45
+ def respond_to?(meth, include_private = false)
46
+ Actor.call @mailbox, :respond_to?, meth, include_private
34
47
  end
35
48
 
36
49
  def methods(include_ancestors = true)
37
50
  Actor.call @mailbox, :methods, include_ancestors
38
51
  end
39
52
 
53
+ def method(name)
54
+ Method.new(self, name)
55
+ end
56
+
40
57
  def alive?
41
58
  @mailbox.alive?
42
59
  end
@@ -65,28 +82,14 @@ module Celluloid
65
82
  # Terminate the associated actor
66
83
  def terminate
67
84
  terminate!
68
- join
85
+ Actor.join(self)
69
86
  nil
70
87
  end
71
88
 
72
89
  # Terminate the associated actor asynchronously
73
90
  def terminate!
74
- raise DeadActorError, "actor already terminated" unless alive?
75
- @mailbox.system_event TerminationRequest.new
76
- end
77
-
78
- # Forcibly kill a given actor
79
- def kill
80
- @thread.kill
81
- begin
82
- @mailbox.shutdown
83
- rescue DeadActorError
84
- end
85
- end
86
-
87
- # Wait for an actor to terminate
88
- def join
89
- @thread.join
91
+ ::Kernel.raise DeadActorError, "actor already terminated" unless alive?
92
+ @mailbox << TerminationRequest.new
90
93
  end
91
94
 
92
95
  # method_missing black magic to call bang predicate methods asynchronously
@@ -0,0 +1,9 @@
1
+ # Things to run after Celluloid is fully loaded
2
+
3
+ # Configure default systemwide settings
4
+ Celluloid.logger = Logger.new STDERR
5
+ Celluloid.task_class = Celluloid::TaskFiber
6
+
7
+ # Launch the notifications fanout actor
8
+ # FIXME: We should set up the supervision hierarchy here
9
+ Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
@@ -9,7 +9,7 @@ module Celluloid
9
9
 
10
10
  def check_signature(obj)
11
11
  unless obj.respond_to? @method
12
- raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
12
+ raise NoMethodError, "undefined method `#{@method}' for #{obj.to_s}"
13
13
  end
14
14
 
15
15
  begin
@@ -39,7 +39,7 @@ module Celluloid
39
39
  class SyncCall < Call
40
40
  attr_reader :caller, :task
41
41
 
42
- def initialize(caller, method, arguments = [], block = nil, task = Fiber.current.task)
42
+ def initialize(caller, method, arguments = [], block = nil, task = Thread.current[:task])
43
43
  super(method, arguments, block)
44
44
  @caller = caller
45
45
  @task = task
@@ -99,7 +99,7 @@ module Celluloid
99
99
  obj.send(@method, *@arguments, &@block)
100
100
  rescue AbortError => ex
101
101
  # Swallow aborted async calls, as they indicate the caller made a mistake
102
- Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex)
102
+ Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex.cause)
103
103
  end
104
104
  end
105
105
  end
@@ -17,9 +17,4 @@ class Thread
17
17
  mailbox.receive(timeout, &block)
18
18
  end
19
19
  end
20
- end
21
-
22
- class Fiber
23
- # Celluloid::Task associated with this Fiber
24
- attr_accessor :task
25
- end
20
+ end
@@ -7,6 +7,8 @@ module Celluloid
7
7
  @cores = Integer(`sysctl hw.ncpu`[/\d+/])
8
8
  when 'linux'
9
9
  @cores = File.read("/proc/cpuinfo").scan(/core id\s+: \d+/).uniq.size
10
+ when 'mingw', 'mswin'
11
+ @cores = Integer(`SET NUMBER_OF_PROCESSORS`[/\d+/])
10
12
  else
11
13
  @cores = nil
12
14
  end
@@ -91,6 +91,9 @@ module Celluloid
91
91
  end
92
92
  alias_method :<<, :signal
93
93
 
94
+ # Is the future ready yet?
95
+ def ready?; @ready; end
96
+
94
97
  # Inspect this Celluloid::Future
95
98
  alias_method :inspect, :to_s
96
99
 
@@ -1,5 +1,3 @@
1
- require 'thread'
2
-
3
1
  module Celluloid
4
2
  # Linked actors send each other system events
5
3
  class Links
@@ -31,7 +29,7 @@ module Celluloid
31
29
 
32
30
  # Send an event message to all actors
33
31
  def send_event(event)
34
- each { |actor| actor.mailbox.system_event event }
32
+ each { |actor| actor.mailbox << event }
35
33
  end
36
34
 
37
35
  # Generate a string representation
@@ -13,9 +13,9 @@ module Celluloid
13
13
  alias_method :address, :object_id
14
14
 
15
15
  def initialize
16
- @messages = []
17
- @mutex = Mutex.new
18
- @dead = false
16
+ @messages = []
17
+ @mutex = Mutex.new
18
+ @dead = false
19
19
  @condition = ConditionVariable.new
20
20
  end
21
21
 
@@ -23,9 +23,18 @@ module Celluloid
23
23
  def <<(message)
24
24
  @mutex.lock
25
25
  begin
26
- raise MailboxError, "dead recipient" if @dead
26
+ if message.is_a?(SystemEvent)
27
+ # Silently swallow system events sent to dead actors
28
+ return if @dead
29
+
30
+ # SystemEvents are high priority messages so they get added to the
31
+ # head of our message queue instead of the end
32
+ @messages.unshift message
33
+ else
34
+ raise MailboxError, "dead recipient" if @dead
35
+ @messages << message
36
+ end
27
37
 
28
- @messages << message
29
38
  @condition.signal
30
39
  nil
31
40
  ensure
@@ -33,20 +42,6 @@ module Celluloid
33
42
  end
34
43
  end
35
44
 
36
- # Add a high-priority system event to the Mailbox
37
- def system_event(event)
38
- @mutex.lock
39
- begin
40
- unless @dead # Silently fail if messages are sent to dead actors
41
- @messages.unshift event
42
- @condition.signal
43
- end
44
- nil
45
- ensure
46
- @mutex.unlock rescue nil
47
- end
48
- end
49
-
50
45
  # Receive a message from the Mailbox
51
46
  def receive(timeout = nil, &block)
52
47
  message = nil
@@ -92,7 +87,6 @@ module Celluloid
92
87
  message = @messages.shift
93
88
  end
94
89
 
95
- raise message if message.is_a? SystemEvent
96
90
  message
97
91
  end
98
92
 
@@ -0,0 +1,19 @@
1
+ module Celluloid
2
+ # Method handles that route through an actor proxy
3
+ class Method
4
+ def initialize(actor, name)
5
+ raise NameError, "undefined method `#{name}'" unless actor.respond_to? name
6
+
7
+ @actor, @name = actor, name
8
+ @klass = @actor.class
9
+ end
10
+
11
+ def call(*args, &block)
12
+ @actor._send_(@name, *args, &block)
13
+ end
14
+
15
+ def inspect
16
+ "#<Celluloid::Method #{@klass}#{@name}>"
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,21 @@
1
1
  module Celluloid
2
2
  module Notifications
3
+ def self.notifier
4
+ Actor[:notifications_fanout] or raise DeadActorError, "notifications fanout actor not running"
5
+ end
6
+
7
+ def publish(pattern, *args)
8
+ Celluloid::Notifications.notifier.publish(pattern, *args)
9
+ end
10
+
11
+ def subscribe(pattern, method)
12
+ Celluloid::Notifications.notifier.subscribe(Actor.current, pattern, method)
13
+ end
14
+
15
+ def unsubscribe(*args)
16
+ Celluloid::Notifications.notifier.unsubscribe(*args)
17
+ end
18
+
3
19
  class Fanout
4
20
  include Celluloid
5
21
  trap_exit :prune
@@ -63,22 +79,5 @@ module Celluloid
63
79
  @pattern && @pattern === subscriber_or_pattern
64
80
  end
65
81
  end
66
-
67
- class << self
68
- attr_accessor :notifier
69
- end
70
- self.notifier = Fanout.new
71
-
72
- def publish(pattern, *args)
73
- Celluloid::Notifications.notifier.publish(pattern, *args)
74
- end
75
-
76
- def subscribe(pattern, method)
77
- Celluloid::Notifications.notifier.subscribe(Actor.current, pattern, method)
78
- end
79
-
80
- def unsubscribe(*args)
81
- Celluloid::Notifications.notifier.unsubscribe(*args)
82
- end
83
82
  end
84
83
  end
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module Celluloid
2
4
  # Manages a fixed-size pool of workers
3
5
  # Delegates work (i.e. methods) and supervises workers
@@ -7,14 +9,28 @@ module Celluloid
7
9
  trap_exit :crash_handler
8
10
 
9
11
  def initialize(worker_class, options = {})
10
- @size = options[:size]
11
- raise ArgumentError, "minimum pool size is 2" if @size && @size < 2
12
+ @size = options[:size] || [Celluloid.cores, 2].max
13
+ raise ArgumentError, "minimum pool size is 2" if @size < 2
12
14
 
13
- @size ||= [Celluloid.cores, 2].max
15
+ @worker_class = worker_class
14
16
  @args = options[:args] ? Array(options[:args]) : []
15
17
 
16
- @worker_class = worker_class
17
18
  @idle = @size.times.map { worker_class.new_link(*@args) }
19
+
20
+ # FIXME: Another data structure (e.g. Set) would be more appropriate
21
+ # here except it causes MRI to crash :o
22
+ @busy = []
23
+ end
24
+
25
+ def finalize
26
+ terminators = (@idle + @busy).each do |actor|
27
+ begin
28
+ actor.future(:terminate)
29
+ rescue DeadActorError, MailboxError
30
+ end
31
+ end
32
+
33
+ terminators.compact.each { |terminator| terminator.value rescue nil }
18
34
  end
19
35
 
20
36
  def _send_(method, *args, &block)
@@ -22,10 +38,17 @@ module Celluloid
22
38
 
23
39
  begin
24
40
  worker._send_ method, *args, &block
41
+ rescue DeadActorError # if we get a dead actor out of the pool
42
+ wait :respawn_complete
43
+ worker = __provision_worker
44
+ retry
25
45
  rescue Exception => ex
26
46
  abort ex
27
47
  ensure
28
- @idle << worker if worker.alive?
48
+ if worker.alive?
49
+ @idle << worker
50
+ @busy.delete worker
51
+ end
29
52
  end
30
53
  end
31
54
 
@@ -56,23 +79,29 @@ module Celluloid
56
79
  # Provision a new worker
57
80
  def __provision_worker
58
81
  while @idle.empty?
59
- # Using exclusive mode blocks incoming messages, so they don't pile
60
- # up as waiting Celluloid::Tasks
61
- response = exclusive { receive { |msg| msg.is_a? Response } }
82
+ # Wait for responses from one of the busy workers
83
+ response = exclusive { receive { |msg| msg.is_a?(Response) } }
62
84
  Thread.current[:actor].handle_message(response)
63
85
  end
64
- @idle.shift
86
+
87
+ worker = @idle.shift
88
+ @busy << worker
89
+
90
+ worker
65
91
  end
66
92
 
67
93
  # Spawn a new worker for every crashed one
68
94
  def crash_handler(actor, reason)
95
+ @busy.delete actor
69
96
  @idle.delete actor
70
- return unless reason # don't restart workers that exit cleanly
97
+ return unless reason
98
+
71
99
  @idle << @worker_class.new_link(*@args)
100
+ signal :respawn_complete
72
101
  end
73
102
 
74
103
  def respond_to?(method)
75
- super || (@worker_class ? @worker_class.instance_methods.include?(method.to_sym) : false)
104
+ super || @worker_class.instance_methods.include?(method.to_sym)
76
105
  end
77
106
 
78
107
  def method_missing(method, *args, &block)
@@ -3,8 +3,8 @@ require 'thread'
3
3
  module Celluloid
4
4
  # The Registry allows us to refer to specific actors by human-meaningful names
5
5
  class Registry
6
- def self.root
7
- @root ||= new
6
+ class << self
7
+ attr_reader :root
8
8
  end
9
9
 
10
10
  def initialize
@@ -23,7 +23,7 @@ module Celluloid
23
23
  @registry[name.to_sym] = actor
24
24
  end
25
25
 
26
- actor.mailbox.system_event NamingRequest.new(name.to_sym)
26
+ actor.mailbox << NamingRequest.new(name.to_sym)
27
27
  end
28
28
 
29
29
  # Retrieve an actor by name
@@ -38,7 +38,7 @@ module Celluloid
38
38
 
39
39
  def delete(name)
40
40
  @registry_lock.synchronize do
41
- @registry[name.to_sym]
41
+ @registry.delete name.to_sym
42
42
  end
43
43
  end
44
44
 
@@ -48,7 +48,7 @@ module Celluloid
48
48
  end
49
49
 
50
50
  # removes and returns all registered actors as a hash of `name => actor`
51
- # can be used in testing to clear the registry
51
+ # can be used in testing to clear the registry
52
52
  def clear
53
53
  hash = nil
54
54
  @registry_lock.synchronize do
@@ -57,5 +57,8 @@ module Celluloid
57
57
  end
58
58
  hash
59
59
  end
60
+
61
+ # Create the default registry
62
+ @root = new
60
63
  end
61
64
  end
@@ -1,2 +1,6 @@
1
+ require File.expand_path('../../../spec/support/example_actor_class', __FILE__)
1
2
  require File.expand_path('../../../spec/support/actor_examples', __FILE__)
2
3
  require File.expand_path('../../../spec/support/mailbox_examples', __FILE__)
4
+
5
+ # Timer accuracy enforced by the tests (50ms)
6
+ TIMER_QUANTUM = 0.05
@@ -28,7 +28,7 @@ module Celluloid
28
28
  # Take five, toplevel supervisor
29
29
  sleep 5 while supervisor.alive?
30
30
 
31
- Logger.error "!!! Celluloid::Group #{self} crashed. Restarting..."
31
+ Logger.error "!!! Celluloid::SupervisionGroup #{self} crashed. Restarting..."
32
32
  end
33
33
  end
34
34
 
@@ -44,9 +44,13 @@ module Celluloid
44
44
  end
45
45
 
46
46
  # Register a pool of actors to be launched on group startup
47
- def pool(klass)
47
+ # Available options are:
48
+ #
49
+ # * as: register this application in the Celluloid::Actor[] directory
50
+ # * args: start the actor pool with the given arguments
51
+ def pool(klass, options = {})
48
52
  blocks << lambda do |group|
49
- group.pool klass
53
+ group.pool klass, options
50
54
  end
51
55
  end
52
56
  end
@@ -67,8 +71,9 @@ module Celluloid
67
71
  add(klass, :args => args, :block => block, :as => name)
68
72
  end
69
73
 
70
- def pool(klass)
71
- add(klass, :method => 'pool_link')
74
+ def pool(klass, options = {})
75
+ options[:method] = 'pool_link'
76
+ add(klass, options)
72
77
  end
73
78
 
74
79
  def add(klass, options)
@@ -106,16 +111,27 @@ module Celluloid
106
111
  options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
107
112
 
108
113
  @name = options['as']
109
- @args = options['args'] ? Array(options['args']) : []
110
114
  @block = options['block']
115
+ @args = options['args'] ? Array(options['args']) : []
111
116
  @method = options['method'] || 'new_link'
117
+ @pool = @method == 'pool_link'
118
+ @pool_size = options['size'] if @pool
112
119
 
113
120
  start
114
121
  end
115
122
  attr_reader :name, :actor
116
123
 
117
124
  def start
118
- @actor = @klass.send(@method, *@args, &@block)
125
+ # when it is a pool, then we don't splat the args
126
+ # and we need to extract the pool size if set
127
+ if @pool
128
+ options = {:args => @args}.tap do |hash|
129
+ hash[:size] = @pool_size if @pool_size
130
+ end
131
+ @actor = @klass.send(@method, options, &@block)
132
+ else
133
+ @actor = @klass.send(@method, *@args, &@block)
134
+ end
119
135
  @registry[@name] = @actor if @name
120
136
  end
121
137
 
@@ -135,7 +151,4 @@ module Celluloid
135
151
  end
136
152
  end
137
153
  end
138
-
139
- # Legacy support for the old name
140
- Group = SupervisionGroup
141
154
  end