celluloid 0.11.1 → 0.12.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.
@@ -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