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.
- data/README.md +23 -6
- data/lib/celluloid.rb +118 -109
- data/lib/celluloid/actor.rb +145 -55
- data/lib/celluloid/actor_proxy.rb +24 -21
- data/lib/celluloid/boot.rb +9 -0
- data/lib/celluloid/calls.rb +3 -3
- data/lib/celluloid/core_ext.rb +1 -6
- data/lib/celluloid/cpu_counter.rb +2 -0
- data/lib/celluloid/future.rb +3 -0
- data/lib/celluloid/links.rb +1 -3
- data/lib/celluloid/mailbox.rb +14 -20
- data/lib/celluloid/method.rb +19 -0
- data/lib/celluloid/notifications.rb +16 -17
- data/lib/celluloid/pool_manager.rb +40 -11
- data/lib/celluloid/registry.rb +8 -5
- data/lib/celluloid/rspec.rb +4 -0
- data/lib/celluloid/supervision_group.rb +23 -10
- data/lib/celluloid/system_events.rb +54 -0
- data/lib/celluloid/task.rb +7 -63
- data/lib/celluloid/tasks/task_fiber.rb +65 -0
- data/lib/celluloid/tasks/task_thread.rb +70 -0
- data/lib/celluloid/thread_handle.rb +2 -1
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +124 -8
- data/spec/support/example_actor_class.rb +1 -1
- data/spec/support/mailbox_examples.rb +3 -18
- data/spec/support/task_examples.rb +36 -0
- metadata +17 -12
- data/lib/celluloid/events.rb +0 -26
@@ -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
|
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
|
data/lib/celluloid/calls.rb
CHANGED
@@ -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.
|
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 =
|
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
|
data/lib/celluloid/core_ext.rb
CHANGED
data/lib/celluloid/future.rb
CHANGED
data/lib/celluloid/links.rb
CHANGED
@@ -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
|
32
|
+
each { |actor| actor.mailbox << event }
|
35
33
|
end
|
36
34
|
|
37
35
|
# Generate a string representation
|
data/lib/celluloid/mailbox.rb
CHANGED
@@ -13,9 +13,9 @@ module Celluloid
|
|
13
13
|
alias_method :address, :object_id
|
14
14
|
|
15
15
|
def initialize
|
16
|
-
@messages
|
17
|
-
@mutex
|
18
|
-
@dead
|
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
|
-
|
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
|
12
|
+
@size = options[:size] || [Celluloid.cores, 2].max
|
13
|
+
raise ArgumentError, "minimum pool size is 2" if @size < 2
|
12
14
|
|
13
|
-
@
|
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
|
-
|
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
|
-
#
|
60
|
-
|
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
|
-
|
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
|
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 ||
|
104
|
+
super || @worker_class.instance_methods.include?(method.to_sym)
|
76
105
|
end
|
77
106
|
|
78
107
|
def method_missing(method, *args, &block)
|
data/lib/celluloid/registry.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
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
|
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
|
data/lib/celluloid/rspec.rb
CHANGED
@@ -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::
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|