kulesa-celluloid 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +116 -0
- data/lib/celluloid/actor.rb +279 -0
- data/lib/celluloid/actor_proxy.rb +95 -0
- data/lib/celluloid/calls.rb +105 -0
- data/lib/celluloid/core_ext.rb +25 -0
- data/lib/celluloid/cpu_counter.rb +16 -0
- data/lib/celluloid/events.rb +26 -0
- data/lib/celluloid/fiber.rb +32 -0
- data/lib/celluloid/fsm.rb +151 -0
- data/lib/celluloid/future.rb +110 -0
- data/lib/celluloid/group.rb +90 -0
- data/lib/celluloid/internal_pool.rb +62 -0
- data/lib/celluloid/links.rb +61 -0
- data/lib/celluloid/logger.rb +53 -0
- data/lib/celluloid/mailbox.rb +134 -0
- data/lib/celluloid/pool.rb +105 -0
- data/lib/celluloid/receivers.rb +70 -0
- data/lib/celluloid/registry.rb +35 -0
- data/lib/celluloid/responses.rb +26 -0
- data/lib/celluloid/rspec.rb +2 -0
- data/lib/celluloid/signals.rb +51 -0
- data/lib/celluloid/supervisor.rb +69 -0
- data/lib/celluloid/task.rb +81 -0
- data/lib/celluloid/thread_handle.rb +35 -0
- data/lib/celluloid/timers.rb +110 -0
- data/lib/celluloid/uuid.rb +38 -0
- data/lib/celluloid/version.rb +4 -0
- data/lib/celluloid/worker.rb +78 -0
- data/lib/celluloid.rb +355 -0
- data/spec/support/actor_examples.rb +565 -0
- data/spec/support/mailbox_examples.rb +52 -0
- metadata +142 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Simple finite state machines with integrated Celluloid timeout support
|
3
|
+
# Inspired by Erlang's gen_fsm (http://www.erlang.org/doc/man/gen_fsm.html)
|
4
|
+
#
|
5
|
+
# Basic usage:
|
6
|
+
#
|
7
|
+
# class MyMachine
|
8
|
+
# include Celluloid::FSM # NOTE: this does NOT pull in the Celluloid module
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# Inside an actor:
|
12
|
+
#
|
13
|
+
# #
|
14
|
+
# machine = MyMachine.new(current_actor)
|
15
|
+
module FSM
|
16
|
+
class UnattachedError < StandardError; end # Not attached to an actor
|
17
|
+
|
18
|
+
DEFAULT_STATE = :default # Default state name unless one is explicitly set
|
19
|
+
|
20
|
+
# Included hook to extend class methods
|
21
|
+
def self.included(klass)
|
22
|
+
klass.send :extend, ClassMethods
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
# Obtain or set the default state
|
27
|
+
# Passing a state name sets the default state
|
28
|
+
def default_state(new_default = nil)
|
29
|
+
if new_default
|
30
|
+
@default_state = new_default.to_sym
|
31
|
+
else
|
32
|
+
defined?(@default_state) ? @default_state : DEFAULT_STATE
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Obtain the valid states for this FSM
|
37
|
+
def states
|
38
|
+
@states ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Declare an FSM state and optionally provide a callback block to fire
|
42
|
+
# Options:
|
43
|
+
# * to: a state or array of states this state can transition to
|
44
|
+
def state(*args, &block)
|
45
|
+
if args.last.is_a? Hash
|
46
|
+
# Stringify keys :/
|
47
|
+
options = args.pop.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
48
|
+
else
|
49
|
+
options = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
args.each do |name|
|
53
|
+
name = name.to_sym
|
54
|
+
default_state name if options['default']
|
55
|
+
states[name] = State.new(name, options['to'], &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :actor
|
61
|
+
|
62
|
+
# Be kind and call super if you must redefine initialize
|
63
|
+
def initialize(actor = nil)
|
64
|
+
@state = self.class.default_state
|
65
|
+
@actor = actor
|
66
|
+
@actor ||= Celluloid.current_actor if Celluloid.actor?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Obtain the current state of the FSM
|
70
|
+
attr_reader :state
|
71
|
+
|
72
|
+
# Attach this FSM to an actor. This allows FSMs to wait for and initiate
|
73
|
+
# events in the context of a particular actor
|
74
|
+
def attach(actor)
|
75
|
+
@actor = actor
|
76
|
+
end
|
77
|
+
alias_method :actor=, :attach
|
78
|
+
|
79
|
+
# Transition to another state
|
80
|
+
# Options:
|
81
|
+
# * delay: don't transition immediately, wait the given number of seconds.
|
82
|
+
# This will return a Celluloid::Timer object you can use to
|
83
|
+
# cancel the pending state transition.
|
84
|
+
#
|
85
|
+
# Note: making additional state transitions will cancel delayed transitions
|
86
|
+
def transition(state_name, options = {})
|
87
|
+
state_name = state_name.to_sym
|
88
|
+
current_state = self.class.states[@state]
|
89
|
+
|
90
|
+
return if current_state && current_state.name == state_name
|
91
|
+
|
92
|
+
if current_state and not current_state.valid_transition? state_name
|
93
|
+
valid = current_state.transitions.map(&:to_s).join(", ")
|
94
|
+
raise ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}"
|
95
|
+
end
|
96
|
+
|
97
|
+
new_state = self.class.states[state_name]
|
98
|
+
|
99
|
+
unless new_state
|
100
|
+
return if state_name == self.class.default_state
|
101
|
+
raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
|
102
|
+
end
|
103
|
+
|
104
|
+
if options[:delay]
|
105
|
+
raise UnattachedError, "can't delay unless attached" unless @actor
|
106
|
+
@delayed_transition.cancel if @delayed_transition
|
107
|
+
|
108
|
+
@delayed_transition = @actor.after(options[:delay]) do
|
109
|
+
transition! new_state.name
|
110
|
+
new_state.call(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
return @delayed_transition
|
114
|
+
end
|
115
|
+
|
116
|
+
if defined?(@delayed_transition) and @delayed_transition
|
117
|
+
@delayed_transition.cancel
|
118
|
+
@delayed_transition = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
transition! new_state.name
|
122
|
+
new_state.call(self)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Immediate state transition with no sanity checks. "Dangerous!"
|
126
|
+
def transition!(state_name)
|
127
|
+
@state = state_name
|
128
|
+
end
|
129
|
+
|
130
|
+
# FSM states as declared by Celluloid::FSM.state
|
131
|
+
class State
|
132
|
+
attr_reader :name, :transitions
|
133
|
+
|
134
|
+
def initialize(name, transitions = nil, &block)
|
135
|
+
@name, @block = name, block
|
136
|
+
@transitions = Array(transitions).map { |t| t.to_sym } if transitions
|
137
|
+
end
|
138
|
+
|
139
|
+
def call(obj)
|
140
|
+
obj.instance_eval(&@block) if @block
|
141
|
+
end
|
142
|
+
|
143
|
+
def valid_transition?(new_state)
|
144
|
+
# All transitions are allowed unless expressly
|
145
|
+
return true unless @transitions
|
146
|
+
|
147
|
+
@transitions.include? new_state.to_sym
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
# Celluloid::Future objects allow methods and blocks to run in the
|
5
|
+
# background, their values requested later
|
6
|
+
class Future
|
7
|
+
# Create a future bound to a given receiver, or with a block to compute
|
8
|
+
def initialize(*args, &block)
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@ready = false
|
11
|
+
@result = nil
|
12
|
+
@forwards = nil
|
13
|
+
|
14
|
+
if block
|
15
|
+
@call = SyncCall.new(self, :call, args)
|
16
|
+
InternalPool.get do
|
17
|
+
begin
|
18
|
+
@call.dispatch(block)
|
19
|
+
rescue
|
20
|
+
# Exceptions in blocks will get raised when the value is retrieved
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@call = nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Execute the given method in future context
|
29
|
+
def execute(receiver, method, args, block)
|
30
|
+
@mutex.synchronize do
|
31
|
+
raise "already calling" if @call
|
32
|
+
@call = SyncCall.new(self, method, args, block)
|
33
|
+
end
|
34
|
+
|
35
|
+
receiver << @call
|
36
|
+
end
|
37
|
+
|
38
|
+
# Obtain the value for this Future
|
39
|
+
def value(timeout = nil)
|
40
|
+
ready = result = nil
|
41
|
+
|
42
|
+
begin
|
43
|
+
@mutex.lock
|
44
|
+
raise "no call requested" unless @call
|
45
|
+
|
46
|
+
if @ready
|
47
|
+
ready = true
|
48
|
+
result = @result
|
49
|
+
else
|
50
|
+
case @forwards
|
51
|
+
when Array
|
52
|
+
@forwards << Thread.mailbox
|
53
|
+
when NilClass
|
54
|
+
@forwards = Thread.mailbox
|
55
|
+
else
|
56
|
+
@forwards = [@forwards, Thread.mailbox]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
ensure
|
60
|
+
@mutex.unlock
|
61
|
+
end
|
62
|
+
|
63
|
+
unless ready
|
64
|
+
result = Thread.receive(timeout) do |msg|
|
65
|
+
msg.is_a?(Future::Result) && msg.future == self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if result
|
70
|
+
result.value
|
71
|
+
else
|
72
|
+
raise "Timed out"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
alias_method :call, :value
|
76
|
+
|
77
|
+
# Signal this future with the given result value
|
78
|
+
def signal(value)
|
79
|
+
result = Result.new(value, self)
|
80
|
+
|
81
|
+
@mutex.synchronize do
|
82
|
+
raise "the future has already happened!" if @ready
|
83
|
+
|
84
|
+
if @forwards
|
85
|
+
@forwards.is_a?(Array) ? @forwards.each { |f| f << result } : @forwards << result
|
86
|
+
end
|
87
|
+
|
88
|
+
@result = result
|
89
|
+
@ready = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
alias_method :<<, :signal
|
93
|
+
|
94
|
+
# Inspect this Celluloid::Future
|
95
|
+
alias_method :inspect, :to_s
|
96
|
+
|
97
|
+
# Wrapper for result values to distinguish them in mailboxes
|
98
|
+
class Result
|
99
|
+
attr_reader :future
|
100
|
+
|
101
|
+
def initialize(result, future)
|
102
|
+
@result, @future = result, future
|
103
|
+
end
|
104
|
+
|
105
|
+
def value
|
106
|
+
@result.value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Supervise collections of actors as a group
|
3
|
+
class Group
|
4
|
+
include Celluloid
|
5
|
+
trap_exit :restart_actor
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Actors or sub-applications to be supervised
|
9
|
+
def members
|
10
|
+
@members ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Start this application (and watch it with a supervisor)
|
14
|
+
alias_method :run!, :supervise
|
15
|
+
|
16
|
+
# Run the application in the foreground with a simple watchdog
|
17
|
+
def run
|
18
|
+
loop do
|
19
|
+
supervisor = run!
|
20
|
+
|
21
|
+
# Take five, toplevel supervisor
|
22
|
+
sleep 5 while supervisor.alive?
|
23
|
+
|
24
|
+
Logger.error "!!! Celluloid::Group #{self} crashed. Restarting..."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register an actor class or a sub-application class to be launched and
|
29
|
+
# supervised while this application is running. Available options are:
|
30
|
+
#
|
31
|
+
# * as: register this application in the Celluloid::Actor[] directory
|
32
|
+
# * args: start the actor with the given arguments
|
33
|
+
def supervise(klass, options = {})
|
34
|
+
members << Member.new(klass, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Start the group
|
39
|
+
def initialize
|
40
|
+
@actors = {}
|
41
|
+
|
42
|
+
# This is some serious lolcode, but like... start the supervisors for
|
43
|
+
# this group
|
44
|
+
self.class.members.each do |member|
|
45
|
+
actor = member.start
|
46
|
+
@actors[actor] = member
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Terminate the group
|
51
|
+
def finalize
|
52
|
+
@actors.each do |actor, _|
|
53
|
+
begin
|
54
|
+
actor.terminate
|
55
|
+
rescue DeadActorError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Restart a crashed actor
|
61
|
+
def restart_actor(actor, reason)
|
62
|
+
member = @actors.delete actor
|
63
|
+
raise "a group member went missing. This shouldn't be!" unless member
|
64
|
+
|
65
|
+
# Ignore supervisors that shut down cleanly
|
66
|
+
return unless reason
|
67
|
+
|
68
|
+
actor = member.start
|
69
|
+
@actors[actor] = member
|
70
|
+
end
|
71
|
+
|
72
|
+
# A member of the group
|
73
|
+
class Member
|
74
|
+
def initialize(klass, options = {})
|
75
|
+
@klass = klass
|
76
|
+
|
77
|
+
# Stringify keys :/
|
78
|
+
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
79
|
+
|
80
|
+
@name = options['as']
|
81
|
+
@args = options['args'] ? Array(options['args']) : []
|
82
|
+
end
|
83
|
+
|
84
|
+
def start
|
85
|
+
actor = @klass.new_link(*@args)
|
86
|
+
Actor[@name] = actor if @name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
# Maintain a thread pool FOR SPEED!!
|
5
|
+
module InternalPool
|
6
|
+
@pool = []
|
7
|
+
@mutex = Mutex.new
|
8
|
+
|
9
|
+
# TODO: should really adjust this based on usage
|
10
|
+
@max_idle = 16
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :max_idle
|
14
|
+
|
15
|
+
# Get a thread from the pool, running the given block
|
16
|
+
def get(&block)
|
17
|
+
@mutex.synchronize do
|
18
|
+
begin
|
19
|
+
if @pool.empty?
|
20
|
+
thread = create
|
21
|
+
else
|
22
|
+
thread = @pool.shift
|
23
|
+
end
|
24
|
+
end until thread.status # handle crashed threads
|
25
|
+
|
26
|
+
thread[:queue] << block
|
27
|
+
thread
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return a thread to the pool
|
32
|
+
def put(thread)
|
33
|
+
@mutex.synchronize do
|
34
|
+
if @pool.size >= @max_idle
|
35
|
+
thread[:queue] << nil
|
36
|
+
else
|
37
|
+
@pool << thread
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a new thread with an associated queue of procs to run
|
43
|
+
def create
|
44
|
+
queue = Queue.new
|
45
|
+
thread = Thread.new do
|
46
|
+
while proc = queue.pop
|
47
|
+
begin
|
48
|
+
proc.call
|
49
|
+
rescue => ex
|
50
|
+
Logger.crash("thread crashed", ex)
|
51
|
+
end
|
52
|
+
|
53
|
+
put thread
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
thread[:queue] = queue
|
58
|
+
thread
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
# Thread safe storage of inter-actor links
|
5
|
+
class Links
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@links = {}
|
10
|
+
@lock = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add an actor to the current links
|
14
|
+
def <<(actor)
|
15
|
+
@lock.synchronize do
|
16
|
+
@links[actor.mailbox.address] = actor
|
17
|
+
end
|
18
|
+
actor
|
19
|
+
end
|
20
|
+
|
21
|
+
# Do links include the given actor?
|
22
|
+
def include?(actor)
|
23
|
+
@lock.synchronize do
|
24
|
+
@links.has_key? actor.mailbox.address
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Remove an actor from the links
|
29
|
+
def delete(actor)
|
30
|
+
@lock.synchronize do
|
31
|
+
@links.delete actor.mailbox.address
|
32
|
+
end
|
33
|
+
actor
|
34
|
+
end
|
35
|
+
|
36
|
+
# Iterate through all links
|
37
|
+
def each
|
38
|
+
@lock.synchronize do
|
39
|
+
@links.each { |_, actor| yield(actor) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Map across links
|
44
|
+
def map
|
45
|
+
result = []
|
46
|
+
each { |actor| result << yield(actor) }
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
# Send an event message to all actors
|
51
|
+
def send_event(event)
|
52
|
+
each { |actor| actor.mailbox.system_event event }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Generate a string representation
|
56
|
+
def inspect
|
57
|
+
links = self.map(&:inspect).join(',')
|
58
|
+
"#<#{self.class}[#{links}]>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module Logger
|
3
|
+
@exception_handlers = []
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Send a debug message
|
7
|
+
def debug(string)
|
8
|
+
Celluloid.logger.debug(string) if Celluloid.logger
|
9
|
+
end
|
10
|
+
|
11
|
+
# Send a info message
|
12
|
+
def info(string)
|
13
|
+
Celluloid.logger.info(string) if Celluloid.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
# Send a warning message
|
17
|
+
def warn(string)
|
18
|
+
Celluloid.logger.warn(string) if Celluloid.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
# Send an error message
|
22
|
+
def error(string)
|
23
|
+
Celluloid.logger.error(string) if Celluloid.logger
|
24
|
+
end
|
25
|
+
|
26
|
+
# Handle a crash
|
27
|
+
def crash(string, exception)
|
28
|
+
string << "\n" << format_exception(exception)
|
29
|
+
error string
|
30
|
+
|
31
|
+
@exception_handlers.each do |handler|
|
32
|
+
begin
|
33
|
+
handler.call(exception)
|
34
|
+
rescue => ex
|
35
|
+
error "EXCEPTION HANDLER CRASHED:\n" << format_exception(ex)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Format an exception message
|
41
|
+
def format_exception(exception)
|
42
|
+
str = "#{exception.class}: #{exception.to_s}\n"
|
43
|
+
str << exception.backtrace.join("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Define an exception handler
|
47
|
+
# NOTE: These should be defined at application start time
|
48
|
+
def exception_handler(&block)
|
49
|
+
@exception_handlers << block
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
class MailboxError < StandardError; end # you can't message the dead
|
5
|
+
class MailboxShutdown < StandardError; end # raised if the mailbox can no longer be used
|
6
|
+
|
7
|
+
# Actors communicate with asynchronous messages. Messages are buffered in
|
8
|
+
# Mailboxes until Actors can act upon them.
|
9
|
+
class Mailbox
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
# A unique address at which this mailbox can be found
|
13
|
+
alias_method :address, :object_id
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@messages = []
|
17
|
+
@mutex = Mutex.new
|
18
|
+
@dead = false
|
19
|
+
@condition = ConditionVariable.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add a message to the Mailbox
|
23
|
+
def <<(message)
|
24
|
+
@mutex.lock
|
25
|
+
begin
|
26
|
+
raise MailboxError, "dead recipient" if @dead
|
27
|
+
|
28
|
+
@messages << message
|
29
|
+
@condition.signal
|
30
|
+
nil
|
31
|
+
ensure
|
32
|
+
@mutex.unlock rescue nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
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
|
+
# Receive a message from the Mailbox
|
51
|
+
def receive(timeout = nil, &block)
|
52
|
+
message = nil
|
53
|
+
|
54
|
+
@mutex.lock
|
55
|
+
begin
|
56
|
+
raise MailboxError, "attempted to receive from a dead mailbox" if @dead
|
57
|
+
|
58
|
+
begin
|
59
|
+
message = next_message(&block)
|
60
|
+
|
61
|
+
unless message
|
62
|
+
if timeout
|
63
|
+
now = Time.now
|
64
|
+
wait_until ||= now + timeout
|
65
|
+
wait_interval = wait_until - now
|
66
|
+
return if wait_interval <= 0
|
67
|
+
else
|
68
|
+
wait_interval = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
@condition.wait(@mutex, wait_interval)
|
72
|
+
end
|
73
|
+
end until message
|
74
|
+
|
75
|
+
message
|
76
|
+
ensure
|
77
|
+
@mutex.unlock rescue nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Retrieve the next message in the mailbox
|
82
|
+
def next_message
|
83
|
+
message = nil
|
84
|
+
|
85
|
+
if block_given?
|
86
|
+
index = @messages.index do |msg|
|
87
|
+
yield(msg) || msg.is_a?(SystemEvent)
|
88
|
+
end
|
89
|
+
|
90
|
+
message = @messages.slice!(index, 1).first if index
|
91
|
+
else
|
92
|
+
message = @messages.shift
|
93
|
+
end
|
94
|
+
|
95
|
+
raise message if message.is_a? SystemEvent
|
96
|
+
message
|
97
|
+
end
|
98
|
+
|
99
|
+
# Shut down this mailbox and clean up its contents
|
100
|
+
def shutdown
|
101
|
+
@mutex.lock
|
102
|
+
begin
|
103
|
+
messages = @messages
|
104
|
+
@messages = []
|
105
|
+
@dead = true
|
106
|
+
ensure
|
107
|
+
@mutex.unlock rescue nil
|
108
|
+
end
|
109
|
+
|
110
|
+
messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
# Is the mailbox alive?
|
115
|
+
def alive?
|
116
|
+
!@dead
|
117
|
+
end
|
118
|
+
|
119
|
+
# Cast to an array
|
120
|
+
def to_a
|
121
|
+
@mutex.synchronize { @messages.dup }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Iterate through the mailbox
|
125
|
+
def each(&block)
|
126
|
+
to_a.each(&block)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Inspect the contents of the Mailbox
|
130
|
+
def inspect
|
131
|
+
"#<#{self.class}:#{object_id.to_s(16)} @messages=[#{map { |m| m.inspect }.join(', ')}]>"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|