celluloid 0.2.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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