celluloid 0.2.2 → 0.5.0
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 +214 -67
- data/lib/celluloid.rb +144 -13
- data/lib/celluloid/actor.rb +152 -246
- data/lib/celluloid/actor_pool.rb +57 -0
- data/lib/celluloid/actor_proxy.rb +29 -19
- data/lib/celluloid/core_ext.rb +9 -2
- data/lib/celluloid/io.rb +50 -0
- data/lib/celluloid/io/actor.rb +10 -0
- data/lib/celluloid/io/mailbox.rb +65 -0
- data/lib/celluloid/io/reactor.rb +63 -0
- data/lib/celluloid/io/waker.rb +43 -0
- data/lib/celluloid/linking.rb +24 -20
- data/lib/celluloid/mailbox.rb +43 -30
- data/lib/celluloid/registry.rb +0 -5
- data/lib/celluloid/rspec.rb +2 -0
- data/lib/celluloid/supervisor.rb +4 -4
- data/lib/celluloid/tcp_server.rb +32 -0
- data/lib/celluloid/version.rb +1 -1
- metadata +45 -45
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
class Actor
|
5
|
+
# Maintain a thread pool of actors FOR SPEED!!
|
6
|
+
class Pool
|
7
|
+
@pool = []
|
8
|
+
@lock = Mutex.new
|
9
|
+
@max_idle = 16
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :max_idle
|
13
|
+
|
14
|
+
def get
|
15
|
+
@lock.synchronize do
|
16
|
+
if @pool.empty?
|
17
|
+
create
|
18
|
+
else
|
19
|
+
@pool.shift
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def put(thread)
|
25
|
+
@lock.synchronize do
|
26
|
+
if @pool.size >= @max_idle
|
27
|
+
thread.kill
|
28
|
+
else
|
29
|
+
@pool << thread
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def create
|
35
|
+
queue = Queue.new
|
36
|
+
thread = Thread.new do
|
37
|
+
begin
|
38
|
+
while true
|
39
|
+
queue.pop.call
|
40
|
+
end
|
41
|
+
rescue Exception => ex
|
42
|
+
# Rubinius hax
|
43
|
+
raise if defined?(Thread::Die) and ex.is_a? Thread::Die
|
44
|
+
|
45
|
+
message = "Celluloid::Actor::Pool internal failure:\n"
|
46
|
+
message << "#{ex.class}: #{ex.to_s}\n"
|
47
|
+
message << ex.backtrace.join("\n")
|
48
|
+
Celluloid.logger.error message if Celluloid.logger
|
49
|
+
end
|
50
|
+
end
|
51
|
+
thread[:queue] = queue
|
52
|
+
thread
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -5,54 +5,64 @@ module Celluloid
|
|
5
5
|
class ActorProxy
|
6
6
|
# FIXME: not nearly enough methods are delegated here
|
7
7
|
attr_reader :mailbox
|
8
|
-
|
8
|
+
|
9
9
|
def initialize(actor, mailbox)
|
10
10
|
@actor, @mailbox = actor, mailbox
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def send(meth, *args, &block)
|
14
14
|
__call :send, meth, *args, &block
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def respond_to?(meth)
|
18
18
|
__call :respond_to?, meth
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def methods(include_ancestors = true)
|
22
22
|
__call :methods, include_ancestors
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def alive?
|
26
26
|
@actor.alive?
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def to_s
|
30
30
|
__call :to_s
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def inspect
|
34
34
|
return __call :inspect if alive?
|
35
35
|
"#<Celluloid::Actor(#{@actor.class}:0x#{@actor.object_id.to_s(16)}) dead>"
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
# Create a Celluloid::Future which calls a given method
|
39
39
|
def future(method_name, *args, &block)
|
40
40
|
Celluloid::Future.new { __call method_name, *args, &block }
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
# Terminate the associated actor
|
44
44
|
def terminate
|
45
45
|
raise DeadActorError, "actor already terminated" unless alive?
|
46
|
-
|
46
|
+
|
47
|
+
begin
|
48
|
+
send :terminate
|
49
|
+
rescue DeadActorError
|
50
|
+
# In certain cases this is thrown during termination. This is likely
|
51
|
+
# a bug in Celluloid's internals, but it shouldn't affect the caller.
|
52
|
+
# FIXME: track this down and fix it, or at the very least log it
|
53
|
+
end
|
54
|
+
|
55
|
+
# Always return nil until a dependable exit value can be obtained
|
56
|
+
nil
|
47
57
|
end
|
48
|
-
|
58
|
+
|
49
59
|
# method_missing black magic to call bang predicate methods asynchronously
|
50
60
|
def method_missing(meth, *args, &block)
|
51
61
|
# bang methods are async calls
|
52
|
-
if meth.to_s.match(/!$/)
|
62
|
+
if meth.to_s.match(/!$/)
|
53
63
|
unbanged_meth = meth.to_s.sub(/!$/, '')
|
54
64
|
our_mailbox = Thread.current.mailbox
|
55
|
-
|
65
|
+
|
56
66
|
begin
|
57
67
|
@mailbox << AsyncCall.new(our_mailbox, unbanged_meth, args, block)
|
58
68
|
rescue MailboxError
|
@@ -61,22 +71,22 @@ module Celluloid
|
|
61
71
|
# async calls should find other ways to deal with actors dying
|
62
72
|
# during an async call (i.e. linking/supervisors)
|
63
73
|
end
|
64
|
-
|
74
|
+
|
65
75
|
return # casts are async and return immediately
|
66
76
|
end
|
67
|
-
|
77
|
+
|
68
78
|
__call(meth, *args, &block)
|
69
79
|
end
|
70
|
-
|
80
|
+
|
71
81
|
#######
|
72
82
|
private
|
73
83
|
#######
|
74
|
-
|
84
|
+
|
75
85
|
# Make a synchronous call to the actor we're proxying to
|
76
86
|
def __call(meth, *args, &block)
|
77
87
|
our_mailbox = Thread.current.mailbox
|
78
88
|
call = SyncCall.new(our_mailbox, meth, args, block)
|
79
|
-
|
89
|
+
|
80
90
|
begin
|
81
91
|
@mailbox << call
|
82
92
|
rescue MailboxError
|
@@ -111,4 +121,4 @@ module Celluloid
|
|
111
121
|
end
|
112
122
|
end
|
113
123
|
end
|
114
|
-
end
|
124
|
+
end
|
data/lib/celluloid/core_ext.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# Monkeypatch Thread to allow lazy access to its Celluloid::Mailbox
|
2
2
|
class Thread
|
3
|
+
# Retrieve the current mailbox or lazily initialize it
|
3
4
|
def mailbox
|
4
|
-
self[:mailbox]
|
5
|
+
self[:mailbox] || begin
|
6
|
+
if Thread.current != self
|
7
|
+
raise "attempt to access an uninitialized mailbox"
|
8
|
+
end
|
9
|
+
|
10
|
+
self[:mailbox] = Celluloid::Mailbox.new
|
11
|
+
end
|
5
12
|
end
|
6
|
-
end
|
13
|
+
end
|
data/lib/celluloid/io.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'celluloid/io/waker'
|
2
|
+
require 'celluloid/io/reactor'
|
3
|
+
require 'celluloid/io/mailbox'
|
4
|
+
require 'celluloid/io/actor'
|
5
|
+
|
6
|
+
module Celluloid
|
7
|
+
# Actors which can run alongside other I/O operations
|
8
|
+
module IO
|
9
|
+
def self.included(klass)
|
10
|
+
klass.send :include, ::Celluloid
|
11
|
+
klass.send :extend, IO::ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
# Class methods added to classes which include Celluloid::IO
|
15
|
+
module ClassMethods
|
16
|
+
# Create a new actor
|
17
|
+
def new(*args, &block)
|
18
|
+
proxy = IO::Actor.new(allocate).proxy
|
19
|
+
proxy.send(:initialize, *args, &block)
|
20
|
+
proxy
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create a new actor and link to the current one
|
24
|
+
def new_link(*args, &block)
|
25
|
+
current_actor = Thread.current[:actor]
|
26
|
+
raise NotActorError, "can't link outside actor context" unless current_actor
|
27
|
+
|
28
|
+
proxy = IO::Actor.new(allocate).proxy
|
29
|
+
current_actor.link proxy
|
30
|
+
proxy.send(:initialize, *args, &block)
|
31
|
+
proxy
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Instance methods
|
37
|
+
#
|
38
|
+
|
39
|
+
# Wait for the given IO object to become readable
|
40
|
+
def wait_readable(io, &block)
|
41
|
+
# Law of demeter be damned!
|
42
|
+
current_actor.mailbox.reactor.wait_readable(io, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Wait for the given IO object to become writeable
|
46
|
+
def wait_writeable(io, &block)
|
47
|
+
current_actor.mailbox.reactor.wait_writeable(io, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
module IO
|
5
|
+
# An alternative implementation of Celluloid::Mailbox using Wakers
|
6
|
+
class Mailbox < Celluloid::Mailbox
|
7
|
+
attr_reader :reactor
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@messages = []
|
11
|
+
@lock = Mutex.new
|
12
|
+
@waker = Waker.new
|
13
|
+
@reactor = Reactor.new(@waker)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Add a message to the Mailbox
|
17
|
+
def <<(message)
|
18
|
+
@lock.synchronize do
|
19
|
+
@messages << message
|
20
|
+
@waker.signal
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
rescue DeadWakerError
|
24
|
+
raise MailboxError, "dead recipient"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add a high-priority system event to the Mailbox
|
28
|
+
def system_event(event)
|
29
|
+
@lock.synchronize do
|
30
|
+
@messages.unshift event
|
31
|
+
|
32
|
+
begin
|
33
|
+
@waker.signal
|
34
|
+
rescue DeadWakerError
|
35
|
+
# Silently fail if messages are sent to dead actors
|
36
|
+
end
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Receive a message from the Mailbox
|
42
|
+
def receive(&block)
|
43
|
+
message = nil
|
44
|
+
|
45
|
+
begin
|
46
|
+
@reactor.run_once do
|
47
|
+
@waker.wait
|
48
|
+
message = next_message(&block)
|
49
|
+
end
|
50
|
+
end until message
|
51
|
+
|
52
|
+
message
|
53
|
+
rescue DeadWakerError
|
54
|
+
shutdown # force shutdown of the mailbox
|
55
|
+
raise MailboxShutdown, "mailbox shutdown called during receive"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Cleanup any IO objects this Mailbox may be using
|
59
|
+
def shutdown
|
60
|
+
@waker.cleanup
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module IO
|
3
|
+
# React to external I/O events. This is kinda sorta supposed to resemble the
|
4
|
+
# Reactor design pattern.
|
5
|
+
class Reactor
|
6
|
+
def initialize(waker)
|
7
|
+
@waker = waker
|
8
|
+
@readers = {}
|
9
|
+
@writers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Wait for the given IO object to become readable
|
13
|
+
def wait_readable(io)
|
14
|
+
monitor_io io, @readers
|
15
|
+
Fiber.yield
|
16
|
+
block_given? ? yield(io) : io
|
17
|
+
end
|
18
|
+
|
19
|
+
# Wait for the given IO object to become writeable
|
20
|
+
def wait_writeable(io)
|
21
|
+
monitor_io io, @writers
|
22
|
+
Fiber.yield
|
23
|
+
block_given? ? yield(io) : io
|
24
|
+
end
|
25
|
+
|
26
|
+
# Run the reactor, waiting for events, and calling the given block if
|
27
|
+
# the reactor is awoken by the waker
|
28
|
+
def run_once
|
29
|
+
readers, writers = select @readers.keys << @waker.io, @writers.keys
|
30
|
+
yield if readers.include? @waker.io
|
31
|
+
|
32
|
+
[[readers, @readers], [writers, @writers]].each do |ios, registered|
|
33
|
+
ios.each do |io|
|
34
|
+
fiber = registered.delete io
|
35
|
+
fiber.resume if fiber
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#######
|
41
|
+
private
|
42
|
+
#######
|
43
|
+
|
44
|
+
def monitor_io(io, set)
|
45
|
+
# zomg ugly type conversion :(
|
46
|
+
unless io.is_a?(IO)
|
47
|
+
if IO.respond_to? :try_convert
|
48
|
+
io = IO.try_convert(io)
|
49
|
+
elsif io.respond_to? :to_io
|
50
|
+
io = io.to_io
|
51
|
+
else raise TypeError, "can't convert #{io.class} into IO"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if set.has_key? io
|
56
|
+
raise ArgumentError, "another method is already waiting on #{io.inspect}"
|
57
|
+
else
|
58
|
+
set[io] = Fiber.current
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module IO
|
3
|
+
class DeadWakerError < StandardError; end # You can't wake the dead
|
4
|
+
|
5
|
+
# Wakes up sleepy threads so that they can check their mailbox
|
6
|
+
# Works like a ConditionVariable, except it's implemented as an IO object so
|
7
|
+
# that it can be multiplexed alongside other IO objects.
|
8
|
+
class Waker
|
9
|
+
PAYLOAD = "\0" # the payload doesn't matter. each byte is a signal
|
10
|
+
def initialize
|
11
|
+
@receiver, @sender = ::IO.pipe
|
12
|
+
end
|
13
|
+
|
14
|
+
# Wakes up the thread that is waiting for this Waker
|
15
|
+
def signal
|
16
|
+
@sender << PAYLOAD
|
17
|
+
nil
|
18
|
+
rescue IOError, Errno::EPIPE, Errno::EBADF
|
19
|
+
raise DeadWakerError, "waker is already dead"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Wait for another thread to signal this Waker
|
23
|
+
def wait
|
24
|
+
byte = @receiver.read(1)
|
25
|
+
raise DeadWakerError, "can't wait on a dead waker" unless byte == PAYLOAD
|
26
|
+
rescue IOError, RuntimeError
|
27
|
+
raise DeadWakerError, "can't wait on a dead waker"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the IO object which will be readable when this Waker is signaled
|
31
|
+
def io
|
32
|
+
@receiver
|
33
|
+
end
|
34
|
+
|
35
|
+
# Clean up the IO objects associated with this waker
|
36
|
+
def cleanup
|
37
|
+
@receiver.close rescue nil
|
38
|
+
@sender.close rescue nil
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/celluloid/linking.rb
CHANGED
@@ -5,12 +5,12 @@ module Celluloid
|
|
5
5
|
# Thread safe storage of inter-actor links
|
6
6
|
class Links
|
7
7
|
include Enumerable
|
8
|
-
|
8
|
+
|
9
9
|
def initialize
|
10
10
|
@links = Set.new
|
11
11
|
@lock = Mutex.new
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
# Add an actor to the current links
|
15
15
|
def <<(actor)
|
16
16
|
@lock.synchronize do
|
@@ -18,14 +18,14 @@ module Celluloid
|
|
18
18
|
end
|
19
19
|
actor
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
# Do links include the given actor?
|
23
23
|
def include?(actor)
|
24
24
|
@lock.synchronize do
|
25
25
|
@links.include? actor
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# Remove an actor from the links
|
30
30
|
def delete(actor)
|
31
31
|
@lock.synchronize do
|
@@ -33,19 +33,19 @@ module Celluloid
|
|
33
33
|
end
|
34
34
|
actor
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
# Iterate through all links
|
38
38
|
def each(&block)
|
39
39
|
@lock.synchronize do
|
40
40
|
@links.each(&block)
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
# Send an event message to all actors
|
45
45
|
def send_event(event)
|
46
46
|
each { |actor| actor.mailbox.system_event event }
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
# Generate a string representation
|
50
50
|
def inspect
|
51
51
|
@lock.synchronize do
|
@@ -54,32 +54,36 @@ module Celluloid
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
# Support for linking actors together so they can crash or react to errors
|
59
59
|
module Linking
|
60
60
|
# Link this actor to another, allowing it to crash or react to errors
|
61
61
|
def link(actor)
|
62
|
-
actor
|
63
|
-
|
62
|
+
current_actor = Thread.current[:actor]
|
63
|
+
|
64
|
+
actor.notify_link(current_actor.proxy)
|
65
|
+
current_actor.notify_link(actor)
|
64
66
|
end
|
65
|
-
|
67
|
+
|
66
68
|
# Remove links to another actor
|
67
69
|
def unlink(actor)
|
68
|
-
actor
|
69
|
-
|
70
|
+
current_actor = Thread.current[:actor]
|
71
|
+
|
72
|
+
actor.notify_unlink(current_actor.proxy)
|
73
|
+
current_actor.notify_unlink(actor)
|
70
74
|
end
|
71
|
-
|
75
|
+
|
72
76
|
def notify_link(actor)
|
73
|
-
|
77
|
+
Thread.current[:actor].links << actor
|
74
78
|
end
|
75
|
-
|
79
|
+
|
76
80
|
def notify_unlink(actor)
|
77
|
-
|
81
|
+
Thread.current[:actor].links.delete actor
|
78
82
|
end
|
79
|
-
|
83
|
+
|
80
84
|
# Is this actor linked to another?
|
81
85
|
def linked_to?(actor)
|
82
|
-
|
86
|
+
Thread.current[:actor].links.include? actor
|
83
87
|
end
|
84
88
|
end
|
85
|
-
end
|
89
|
+
end
|