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