emissary 1.3.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/LICENSE +203 -0
- data/README.txt +54 -0
- data/VERSION.yml +4 -0
- data/bin/emissary +196 -0
- data/bin/emissary-setup +75 -0
- data/etc/emissary/config.ini +13 -0
- data/etc/init.d/emissary +50 -0
- data/lib/emissary.rb +223 -0
- data/lib/emissary/agent.rb +61 -0
- data/lib/emissary/agent/emissary.rb +163 -0
- data/lib/emissary/agent/error.rb +26 -0
- data/lib/emissary/agent/file.rb +26 -0
- data/lib/emissary/agent/gem.rb +42 -0
- data/lib/emissary/agent/mysql.rb +219 -0
- data/lib/emissary/agent/ping.rb +37 -0
- data/lib/emissary/agent/proxy.rb +26 -0
- data/lib/emissary/agent/rabbitmq.rb +233 -0
- data/lib/emissary/agent/sshkeys.rb +152 -0
- data/lib/emissary/agent/stats.rb +96 -0
- data/lib/emissary/agent/test.rb +40 -0
- data/lib/emissary/config.rb +231 -0
- data/lib/emissary/core_ext/blank.rb +60 -0
- data/lib/emissary/core_ext/misc_object.rb +21 -0
- data/lib/emissary/core_ext/symbolize.rb +33 -0
- data/lib/emissary/daemon.rb +404 -0
- data/lib/emissary/errors.rb +106 -0
- data/lib/emissary/gem_helper.rb +183 -0
- data/lib/emissary/identity.rb +183 -0
- data/lib/emissary/identity/ec2.rb +64 -0
- data/lib/emissary/identity/unix.rb +67 -0
- data/lib/emissary/logger.rb +130 -0
- data/lib/emissary/message.rb +217 -0
- data/lib/emissary/operator.rb +274 -0
- data/lib/emissary/operator/amqp.rb +203 -0
- data/lib/emissary/server.rb +98 -0
- data/lib/emissary/servolux.rb +75 -0
- metadata +262 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
require 'monitor'
|
17
|
+
require 'work_queue'
|
18
|
+
|
19
|
+
module Emissary
|
20
|
+
module OperatorStatistics
|
21
|
+
RX_COUNT_MUTEX = Mutex.new
|
22
|
+
TX_COUNT_MUTEX = Mutex.new
|
23
|
+
|
24
|
+
attr_reader :rx_count, :tx_count
|
25
|
+
def increment_tx_count
|
26
|
+
TX_COUNT_MUTEX.synchronize {
|
27
|
+
@tx_count = (@tx_count + 1 rescue 1)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def tx_count
|
32
|
+
count = 0
|
33
|
+
TX_COUNT_MUTEX.synchronize {
|
34
|
+
count = @tx_count
|
35
|
+
@tx_count = 0
|
36
|
+
}
|
37
|
+
count
|
38
|
+
end
|
39
|
+
|
40
|
+
def increment_rx_count
|
41
|
+
RX_COUNT_MUTEX.synchronize {
|
42
|
+
@rx_count = (@rx_count + 1 rescue 1)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def rx_count
|
47
|
+
count = 0
|
48
|
+
RX_COUNT_MUTEX.synchronize {
|
49
|
+
count = @rx_count
|
50
|
+
@rx_count = 0
|
51
|
+
}
|
52
|
+
count
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Operator
|
57
|
+
include Emissary::OperatorStatistics
|
58
|
+
|
59
|
+
DEFAULT_STATUS_INTERVAL = 3600
|
60
|
+
DEFAULT_MAX_WORKERS = 50
|
61
|
+
MAX_WORKER_TTL = 60
|
62
|
+
|
63
|
+
attr_reader :config, :shutting_down, :signature
|
64
|
+
|
65
|
+
# Override .new so subclasses don't have to call super and can ignore
|
66
|
+
# connection-specific arguments
|
67
|
+
#
|
68
|
+
def self.new(config, *args)
|
69
|
+
allocate.instance_eval do
|
70
|
+
# Store signature
|
71
|
+
@signature = config[:signature]
|
72
|
+
|
73
|
+
# Call a superclass's #initialize if it has one
|
74
|
+
initialize(config, *args)
|
75
|
+
|
76
|
+
# post initialize callback
|
77
|
+
post_init
|
78
|
+
|
79
|
+
# set signature nil
|
80
|
+
@signature ||= Digest::MD5.hexdigest(config.to_s)
|
81
|
+
|
82
|
+
self
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(config, *args)
|
87
|
+
@config = config
|
88
|
+
@workers = (args[0][:max_workers] || DEFAULT_MAX_WORKERS rescue DEFAULT_MAX_WORKERS)
|
89
|
+
|
90
|
+
@agents = WorkQueue.new(@workers, nil, MAX_WORKER_TTL)
|
91
|
+
@publisher = WorkQueue.new(@workers, nil, MAX_WORKER_TTL)
|
92
|
+
|
93
|
+
@timer = nil
|
94
|
+
@stats = WorkQueue.new(1, nil, MAX_WORKER_TTL)
|
95
|
+
|
96
|
+
@rx_count = 0
|
97
|
+
@tx_count = 0
|
98
|
+
|
99
|
+
@shutting_down = false
|
100
|
+
@connected = false
|
101
|
+
end
|
102
|
+
|
103
|
+
def connected?() @connected; end
|
104
|
+
|
105
|
+
def post_init
|
106
|
+
end
|
107
|
+
|
108
|
+
def connect
|
109
|
+
raise NotImplementedError, 'The connect method must be defined by the operator module'
|
110
|
+
end
|
111
|
+
|
112
|
+
def subscribe
|
113
|
+
raise NotImplementedError, 'The subscrie method must be defined by the operator module'
|
114
|
+
end
|
115
|
+
|
116
|
+
def unsubscribe
|
117
|
+
raise NotImplementedError, 'The unsubscribe method must be defined by the operator module'
|
118
|
+
end
|
119
|
+
|
120
|
+
def acknowledge message
|
121
|
+
end
|
122
|
+
|
123
|
+
def reject message, requeue = true
|
124
|
+
end
|
125
|
+
|
126
|
+
def send_data
|
127
|
+
raise NotImplementedError, 'The send_data method must be defined by the operator module'
|
128
|
+
end
|
129
|
+
|
130
|
+
def close
|
131
|
+
raise NotImplementedError, 'The close method must be defined by the operator module'
|
132
|
+
end
|
133
|
+
|
134
|
+
def run
|
135
|
+
@connected = !!connect
|
136
|
+
subscribe
|
137
|
+
schedule_statistics_gatherer
|
138
|
+
notify :startup
|
139
|
+
connected?
|
140
|
+
end
|
141
|
+
|
142
|
+
def disconnect
|
143
|
+
close
|
144
|
+
@connected = false
|
145
|
+
end
|
146
|
+
|
147
|
+
def shutting_down?() @shutting_down; end
|
148
|
+
|
149
|
+
def shutdown!
|
150
|
+
unless shutting_down?
|
151
|
+
@shutting_down = true
|
152
|
+
|
153
|
+
Emissary.logger.info "Cancelling periodic timer for statistics gatherer..."
|
154
|
+
@timer.cancel
|
155
|
+
|
156
|
+
Emissary.logger.notice "Shutting down..."
|
157
|
+
notify :shutdown
|
158
|
+
|
159
|
+
Emissary.logger.info "Shutting down agent workqueue..."
|
160
|
+
@agents.join
|
161
|
+
|
162
|
+
Emissary.logger.info "Shutting down publisher workqueue..."
|
163
|
+
@publisher.join
|
164
|
+
|
165
|
+
Emissary.logger.info "Disconnecting..."
|
166
|
+
disconnect
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def enabled? what
|
171
|
+
unless [ :startup, :shutdown, :stats ].include? what.to_sym
|
172
|
+
Emissary.logger.debug "Testing '#{what}' - it's disabled. Not a valid option."
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
|
176
|
+
unless config[what]
|
177
|
+
Emissary.logger.debug "Testing '#{what}' - it's disabled. Missing from configuration."
|
178
|
+
return false
|
179
|
+
end
|
180
|
+
|
181
|
+
if (config[:disable]||[]).include? what.to_s
|
182
|
+
Emissary.logger.debug "Testing '#{what}' - it's disabled. Listed in 'disable' configuration option."
|
183
|
+
return false
|
184
|
+
end
|
185
|
+
|
186
|
+
Emissary.logger.debug "Testing '#{what}' - it's enabled.."
|
187
|
+
return true
|
188
|
+
end
|
189
|
+
|
190
|
+
def received message
|
191
|
+
acknowledge message
|
192
|
+
end
|
193
|
+
|
194
|
+
def rejected message, opts = { :requeue => true }
|
195
|
+
reject message, opts
|
196
|
+
end
|
197
|
+
|
198
|
+
def receive message
|
199
|
+
@agents.enqueue_b {
|
200
|
+
begin
|
201
|
+
raise message.errors.first unless message.errors.empty? or not message.errors.first.is_a? Exception
|
202
|
+
Emissary.logger.debug " ---> [DISPATCHER] Dispatching new message ... "
|
203
|
+
Emissary.dispatch(message, config, self).activate
|
204
|
+
# ack message if need be (operator dependant)
|
205
|
+
received message
|
206
|
+
rescue ::Emissary::Error::InvalidMessageFormat => e
|
207
|
+
Emissary.logger.warning e.message
|
208
|
+
rejected message, :requeue => true
|
209
|
+
# if it was an encoding error, then we are done - nothing more we can do
|
210
|
+
rescue Exception => e
|
211
|
+
Emissary.logger.error "AgentThread Error: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
212
|
+
send message.error(e)
|
213
|
+
rejected message, :requeue => true
|
214
|
+
else
|
215
|
+
increment_rx_count
|
216
|
+
end
|
217
|
+
Emissary.logger.debug " ---> [DISPATCHER] tasks/workers: #{@agents.cur_tasks}/#{@agents.cur_threads}"
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def send message
|
222
|
+
@publisher.enqueue_b {
|
223
|
+
Emissary.logger.debug " ---> [PUBLISHER] Sending new message ... "
|
224
|
+
begin
|
225
|
+
unless message.will_loop?
|
226
|
+
Emissary.logger.debug "[PUBLISHER] -- Sending message..."
|
227
|
+
send_data message
|
228
|
+
increment_tx_count
|
229
|
+
else
|
230
|
+
Emissary.logger.notice "Not sending message destined for myself - would loop."
|
231
|
+
end
|
232
|
+
rescue Exception => e
|
233
|
+
Emissary.logger.error "PublisherThread Error: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
234
|
+
@shutting_down = true
|
235
|
+
end
|
236
|
+
Emissary.logger.debug " ---> [PUBLISHER] tasks/workers: #{@publisher.cur_tasks}/#{@publisher.cur_threads}"
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
def notify type
|
241
|
+
return unless enabled? type and EM.reactor_running?
|
242
|
+
|
243
|
+
message = Emissary::Message.new(:data => { :agent => :emissary, :method => type })
|
244
|
+
case type
|
245
|
+
when :startup, :shutdown
|
246
|
+
message.recipient = config[type]
|
247
|
+
when :stats
|
248
|
+
message.agent = :stats
|
249
|
+
message.method = :gather
|
250
|
+
end
|
251
|
+
|
252
|
+
Emissary.logger.notice "Running #{type.to_s.capitalize} Notifier"
|
253
|
+
receive message
|
254
|
+
end
|
255
|
+
|
256
|
+
def schedule_statistics_gatherer
|
257
|
+
stats_interval = enabled?(:stats) && config[:stats][:interval] ? config[:stats][:interval].to_i : DEFAULT_STATUS_INTERVAL
|
258
|
+
|
259
|
+
# setup agent to process sending of messages
|
260
|
+
@timer = EM.add_periodic_timer(stats_interval) do
|
261
|
+
rx = rx_count; tx = tx_count
|
262
|
+
rx_throughput = sprintf "%0.4f", (rx.to_f / stats_interval.to_f)
|
263
|
+
tx_throughput = sprintf "%0.4f", (tx.to_f / stats_interval.to_f)
|
264
|
+
|
265
|
+
Emissary.logger.notice "[statistics] publisher tasks/workers: #{@publisher.cur_tasks}/#{@publisher.cur_threads}"
|
266
|
+
Emissary.logger.notice "[statistics] dispatcher tasks/workers: #{@agents.cur_tasks}/#{@agents.cur_threads}"
|
267
|
+
Emissary.logger.notice "[statistics] #{tx} in #{stats_interval} seconds - tx rate: #{tx_throughput}/sec"
|
268
|
+
Emissary.logger.notice "[statistics] #{rx} in #{stats_interval} seconds - rx rate: #{rx_throughput}/sec"
|
269
|
+
|
270
|
+
notify :stats
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
require 'eventmachine'
|
17
|
+
require 'mq'
|
18
|
+
require 'uri'
|
19
|
+
|
20
|
+
module Emissary
|
21
|
+
class Operator
|
22
|
+
module AMQP
|
23
|
+
|
24
|
+
class InvalidExchange < ArgumentError; end
|
25
|
+
class InvalidConfig < StandardError; end
|
26
|
+
|
27
|
+
REQUIRED_KEYS = [ :uri, :subscriptions ]
|
28
|
+
VALID_EXCHANGES = [ :headers, :topic, :direct, :fanout ]
|
29
|
+
|
30
|
+
attr_accessor :subscriptions
|
31
|
+
attr_accessor :not_acked
|
32
|
+
|
33
|
+
@@queue_count = 1
|
34
|
+
|
35
|
+
def validate_config!
|
36
|
+
errors = []
|
37
|
+
errors << 'config not a hash!' unless config.instance_of? Hash
|
38
|
+
|
39
|
+
REQUIRED_KEYS.each do |key|
|
40
|
+
errors << "missing required option '#{key}'" unless config.has_key? key
|
41
|
+
end
|
42
|
+
|
43
|
+
u = ::URI.parse(config[:uri])
|
44
|
+
errors << "URI scheme /must/ be one of 'amqp' or 'amqps'" unless !!u.scheme.match(/^amqps{0,1}$/)
|
45
|
+
[ :user, :password, :host, :path ].each do |v|
|
46
|
+
errors << "invalid value 'nil' for URI part [#{v}]" if u.respond_to? v and u.send(v).nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
raise errors.join("\n") unless errors.empty?
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
def post_init
|
54
|
+
uri = ::URI.parse @config[:uri]
|
55
|
+
ssl = (uri.scheme.to_sym == :amqps)
|
56
|
+
|
57
|
+
@connect_details = {
|
58
|
+
:host => uri.host,
|
59
|
+
:ssl => ssl,
|
60
|
+
:user => (::URI.decode(uri.user) rescue nil) || 'guest',
|
61
|
+
:pass => (::URI.decode(uri.password) rescue nil) || 'guest',
|
62
|
+
:vhost => (! uri.path.empty? ? uri.path : '/nimbul'),
|
63
|
+
:port => uri.port || (ssl ? 5671 : 5672),
|
64
|
+
:logging => !!@config[:debug],
|
65
|
+
}
|
66
|
+
|
67
|
+
# normalize the subscriptions
|
68
|
+
@subscriptions = @config[:subscriptions].inject({}) do |hash,queue|
|
69
|
+
key, type = queue.split(':')
|
70
|
+
type = type.nil? ? DEFAULT_EXCHANGE : (VALID_EXCHANGES.include?(type.to_sym) ? type.to_sym : DEFAULT_EXCHANGE)
|
71
|
+
(hash[type] ||= []) << key
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
# one unique receiving queue per connection
|
76
|
+
@queue_name = "#{Emissary.identity.queue_name}.#{@@queue_count}"
|
77
|
+
@@queue_count += 1
|
78
|
+
|
79
|
+
@not_acked = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def connect
|
83
|
+
if @connect_details[:ssl] and not EM.ssl?
|
84
|
+
raise ::Emissary::Error::ConnectionError ,
|
85
|
+
"Requested SSL connection but EventMachine not compiled with SSL support - quitting!"
|
86
|
+
end
|
87
|
+
|
88
|
+
@message_pool = Queue.new
|
89
|
+
|
90
|
+
@connection = ::AMQP.connect(@connect_details)
|
91
|
+
@channel = ::MQ.new(@connection)
|
92
|
+
|
93
|
+
@queue_config = {
|
94
|
+
:durable => @config[:queue_durable].nil? ? false : @config[:queue_durable],
|
95
|
+
:auto_delete => @config[:queue_auto_delete].nil? ? true : @config[:queue_auto_delete],
|
96
|
+
:exclusive => @config[:queue_exclusive].nil? ? true : @config[:queue_exclusive]
|
97
|
+
}
|
98
|
+
|
99
|
+
@queue = ::MQ::Queue.new(@channel, @queue_name, @queue_config)
|
100
|
+
|
101
|
+
@exchanges = {}
|
102
|
+
@exchanges[:topic] = ::MQ::Exchange.new(@channel, :topic, 'amq.topic')
|
103
|
+
@exchanges[:fanout] = ::MQ::Exchange.new(@channel, :fanout, 'amq.fanout')
|
104
|
+
@exchanges[:direct] = ::MQ::Exchange.new(@channel, :direct, 'amq.direct')
|
105
|
+
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
def subscribe
|
110
|
+
@subscriptions.each do |exchange, keys|
|
111
|
+
keys.map do |routing_key|
|
112
|
+
Emissary.logger.debug "Subscribing To Key: '#{routing_key}' on Exchange '#{exchange}'"
|
113
|
+
@queue.bind(@exchanges[exchange], :key => routing_key)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# now bind to our name directly so we get messages that are
|
118
|
+
# specifically for us
|
119
|
+
@queue.bind(@exchanges[:direct], :key => Emissary.identity.queue_name)
|
120
|
+
|
121
|
+
@queue.subscribe(:ack => true) do |info, message|
|
122
|
+
begin
|
123
|
+
message = Emissary::Message.decode(message).stamp_received!
|
124
|
+
rescue ::Emissary::Error::InvalidMessageFormat => e
|
125
|
+
message = Emissary::Message.new
|
126
|
+
message.errors << e
|
127
|
+
end
|
128
|
+
|
129
|
+
@not_acked[message.uuid] = info
|
130
|
+
Emissary.logger.debug "Received through '#{info.exchange}' and routing key '#{info.routing_key}'"
|
131
|
+
|
132
|
+
receive message
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def unsubscribe
|
137
|
+
@subscriptions.each do |exchange, keys|
|
138
|
+
keys.map do |routing_key|
|
139
|
+
Emissary.logger.info "Unsubscribing from '#{routing_key}' on Exchange '#{exchange}'"
|
140
|
+
@queue.unbind(@exchanges[exchange], :key => routing_key)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
Emissary.logger.info "Unsubscribing from my own queue."
|
145
|
+
@queue.unbind(@exchanges[:direct], :key => Emissary.identity.queue_name)
|
146
|
+
|
147
|
+
Emissary.logger.info "Cancelling all subscriptions."
|
148
|
+
@queue.unsubscribe # could get away with only calling this but, the above "feels" cleaner
|
149
|
+
end
|
150
|
+
|
151
|
+
def send_data msg
|
152
|
+
begin
|
153
|
+
Emissary.logger.debug "Sending message through exchange '#{msg.exchange_type.to_s}' with routing key '#{msg.routing_key}'"
|
154
|
+
Emissary.logger.debug "Message Originator: #{msg.originator} - Recipient: #{msg.recipient}"
|
155
|
+
@exchanges[msg.exchange_type].publish msg.stamp_sent!.encode, :routing_key => msg.routing_key
|
156
|
+
rescue NoMethodError
|
157
|
+
raise InvalidExchange, "publish request on invalid exchange '#{msg.exchange_type}' with routing key '#{msg.routing_key}'"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def acknowledge message
|
162
|
+
unless message.kind_of? Emissary::Message
|
163
|
+
Emissary.logger.warning "Can't acknowledge message not deriving from Emissary::Message class"
|
164
|
+
end
|
165
|
+
|
166
|
+
@not_acked.delete(message.uuid).ack
|
167
|
+
Emissary.logger.debug "Acknowledged Message ID: #{message.uuid}"
|
168
|
+
rescue NoMethodError
|
169
|
+
Emissary.logger.warning "Message with UUID #{message.uuid} not acknowledged."
|
170
|
+
rescue Exception => e
|
171
|
+
Emissary.logger.error "Error in Emissary::Operator::AMQP#acknowledge: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
172
|
+
end
|
173
|
+
|
174
|
+
def reject message, opts = { :requeue => true }
|
175
|
+
return true # currently not implemented in RabbitMQ 1.7.x (coming in versions supporting 0.9.1 AMQP spec)
|
176
|
+
unless message.kind_of? Emissary::Message
|
177
|
+
Emissary.logger.warning "Unable to reject message not deriving from Emissary::Message class"
|
178
|
+
end
|
179
|
+
|
180
|
+
@not_acked.delete(message.uuid).reject(opts)
|
181
|
+
Emissary.logger.debug "Rejected Message ID: #{message.uuid}"
|
182
|
+
rescue Exception => e
|
183
|
+
Emissary.logger.error "Error in AMQP::Reject: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def close
|
187
|
+
# Note: NOT currently supported by current version of RabbitMQ (1.7.x)
|
188
|
+
#Emissary.logger.info "Requeueing unacknowledged messages"
|
189
|
+
#@not_acked.each { |i| i.reject :requeue => true }
|
190
|
+
|
191
|
+
# unfortunately, due to the nature of amqp's deferred asyncronous handling of data send/recv
|
192
|
+
# and no ability to determine whether our shutdown message was /actually/ sent, we have to resort
|
193
|
+
# to sleeping for 1s to ensure our message went out
|
194
|
+
sleep 1 # XXX: HACK HACK HACK - BAD BAD BAD :-(
|
195
|
+
unsubscribe
|
196
|
+
::AMQP.stop
|
197
|
+
end
|
198
|
+
|
199
|
+
def status
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|