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.
@@ -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
- terminate!
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
@@ -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] ||= Celluloid::Mailbox.new
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
@@ -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,10 @@
1
+ module Celluloid
2
+ module IO
3
+ class Actor < Celluloid::Actor
4
+ # Use the special Celluloid::IO::Mailbox to handle incoming requests
5
+ def initialize_mailbox
6
+ Celluloid::IO::Mailbox.new
7
+ end
8
+ end
9
+ end
10
+ 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
@@ -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.notify_link(@_proxy)
63
- self.notify_link(actor)
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.notify_unlink(@_proxy)
69
- self.notify_unlink(actor)
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
- @_links << actor
77
+ Thread.current[:actor].links << actor
74
78
  end
75
-
79
+
76
80
  def notify_unlink(actor)
77
- @_links.delete actor
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
- @_links.include? actor
86
+ Thread.current[:actor].links.include? actor
83
87
  end
84
88
  end
85
- end
89
+ end