kulesa-celluloid 0.10.2
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 +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
|