bunny 0.9.0.pre6 → 0.9.0.pre7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/ChangeLog.md +56 -0
- data/Gemfile +5 -0
- data/README.md +12 -6
- data/bunny.gemspec +1 -1
- data/examples/connection/automatic_recovery_with_basic_get.rb +34 -0
- data/examples/connection/automatic_recovery_with_client_named_queues.rb +6 -3
- data/examples/connection/automatic_recovery_with_server_named_queues.rb +6 -3
- data/lib/bunny/channel.rb +958 -90
- data/lib/bunny/channel_id_allocator.rb +10 -1
- data/lib/bunny/consumer.rb +44 -10
- data/lib/bunny/exceptions.rb +9 -0
- data/lib/bunny/exchange.rb +91 -12
- data/lib/bunny/main_loop.rb +27 -23
- data/lib/bunny/queue.rb +129 -9
- data/lib/bunny/session.rb +118 -24
- data/lib/bunny/system_timer.rb +5 -1
- data/lib/bunny/transport.rb +8 -5
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/basic_ack_spec.rb +5 -3
- data/spec/higher_level_api/integration/basic_consume_spec.rb +30 -2
- data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -0
- data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -47
- data/spec/higher_level_api/integration/basic_reject_spec.rb +22 -0
- data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +24 -0
- data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +63 -0
- data/spec/issues/issue97_attachment.json +1 -0
- data/spec/issues/issue97_spec.rb +174 -0
- metadata +214 -197
data/lib/bunny/session.rb
CHANGED
@@ -15,21 +15,28 @@ require "amq/protocol/client"
|
|
15
15
|
require "amq/settings"
|
16
16
|
|
17
17
|
module Bunny
|
18
|
+
# Represents AMQP 0.9.1 connection (connection to RabbitMQ).
|
19
|
+
# @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide
|
18
20
|
class Session
|
19
21
|
|
22
|
+
# Default host used for connection
|
20
23
|
DEFAULT_HOST = "127.0.0.1"
|
24
|
+
# Default virtual host used for connection
|
21
25
|
DEFAULT_VHOST = "/"
|
26
|
+
# Default username used for connection
|
22
27
|
DEFAULT_USER = "guest"
|
28
|
+
# Default password used for connection
|
23
29
|
DEFAULT_PASSWORD = "guest"
|
24
|
-
# the same value as RabbitMQ 3.0 uses.
|
30
|
+
# Default heartbeat interval, the same value as RabbitMQ 3.0 uses.
|
25
31
|
DEFAULT_HEARTBEAT = 600
|
26
|
-
#
|
32
|
+
# @private
|
27
33
|
DEFAULT_FRAME_MAX = 131072
|
28
34
|
|
29
35
|
# backwards compatibility
|
36
|
+
# @private
|
30
37
|
CONNECT_TIMEOUT = Transport::DEFAULT_CONNECTION_TIMEOUT
|
31
38
|
|
32
|
-
|
39
|
+
# RabbitMQ client metadata
|
33
40
|
DEFAULT_CLIENT_PROPERTIES = {
|
34
41
|
:capabilities => {
|
35
42
|
:publisher_confirms => true,
|
@@ -40,7 +47,7 @@ module Bunny
|
|
40
47
|
:product => "Bunny",
|
41
48
|
:platform => ::RUBY_DESCRIPTION,
|
42
49
|
:version => Bunny::VERSION,
|
43
|
-
:information => "http://
|
50
|
+
:information => "http://rubybunny.info",
|
44
51
|
}
|
45
52
|
|
46
53
|
DEFAULT_LOCALE = "en_GB"
|
@@ -50,7 +57,7 @@ module Bunny
|
|
50
57
|
# API
|
51
58
|
#
|
52
59
|
|
53
|
-
attr_reader :status, :host, :port, :heartbeat, :user, :pass, :vhost, :frame_max
|
60
|
+
attr_reader :status, :host, :port, :heartbeat, :user, :pass, :vhost, :frame_max, :threaded
|
54
61
|
attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
|
55
62
|
attr_reader :default_channel
|
56
63
|
attr_reader :channel_id_allocator
|
@@ -59,6 +66,21 @@ module Bunny
|
|
59
66
|
attr_reader :mechanism
|
60
67
|
|
61
68
|
|
69
|
+
# @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options
|
70
|
+
# @param [Hash] optz Extra options not related to connection
|
71
|
+
#
|
72
|
+
# @option connection_string_or_opts [String] :host ("127.0.0.1") Hostname or IP address to connect to
|
73
|
+
# @option connection_string_or_opts [Integer] :port (5672) Port RabbitMQ listens on
|
74
|
+
# @option connection_string_or_opts [String] :username ("guest") Username
|
75
|
+
# @option connection_string_or_opts [String] :password ("guest") Password
|
76
|
+
# @option connection_string_or_opts [String] :vhost ("/") Virtual host to use
|
77
|
+
# @option connection_string_or_opts [Integer] :heartbeat (600) Heartbeat interval. 0 means no heartbeat.
|
78
|
+
#
|
79
|
+
# @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
|
80
|
+
# @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
|
81
|
+
#
|
82
|
+
# @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide
|
83
|
+
# @api public
|
62
84
|
def initialize(connection_string_or_opts = Hash.new, optz = Hash.new)
|
63
85
|
opts = case (ENV["RABBITMQ_URL"] || connection_string_or_opts)
|
64
86
|
when nil then
|
@@ -77,6 +99,7 @@ module Bunny
|
|
77
99
|
@vhost = self.vhost_from(opts)
|
78
100
|
@logfile = opts[:logfile]
|
79
101
|
@logging = opts[:logging] || false
|
102
|
+
@threaded = opts.fetch(:threaded, true)
|
80
103
|
|
81
104
|
@status = :not_connected
|
82
105
|
|
@@ -95,21 +118,29 @@ module Bunny
|
|
95
118
|
@continuations = ::Queue.new
|
96
119
|
end
|
97
120
|
|
121
|
+
# @return [String] RabbitMQ hostname (or IP address) used
|
98
122
|
def hostname; self.host; end
|
123
|
+
# @return [String] Username used
|
99
124
|
def username; self.user; end
|
125
|
+
# @return [String] Password used
|
100
126
|
def password; self.pass; end
|
127
|
+
# @return [String] Virtual host used
|
101
128
|
def virtual_host; self.vhost; end
|
102
129
|
|
130
|
+
# @return [Boolean] true if this connection uses TLS (SSL)
|
103
131
|
def uses_tls?
|
104
132
|
@transport.uses_tls?
|
105
133
|
end
|
106
134
|
alias tls? uses_tls?
|
107
135
|
|
136
|
+
# @return [Boolean] true if this connection uses TLS (SSL)
|
108
137
|
def uses_ssl?
|
109
138
|
@transport.uses_ssl?
|
110
139
|
end
|
111
140
|
alias ssl? uses_ssl?
|
112
141
|
|
142
|
+
# Starts connection process
|
143
|
+
# @api public
|
113
144
|
def start
|
114
145
|
@continuations = ::Queue.new
|
115
146
|
@status = :connecting
|
@@ -120,12 +151,20 @@ module Bunny
|
|
120
151
|
self.open_connection
|
121
152
|
|
122
153
|
@event_loop = nil
|
123
|
-
self.start_main_loop
|
154
|
+
self.start_main_loop if @threaded
|
124
155
|
|
125
156
|
@default_channel = self.create_channel
|
126
157
|
end
|
127
158
|
|
159
|
+
def read_write_timeout
|
160
|
+
@transport.read_write_timeout
|
161
|
+
end
|
128
162
|
|
163
|
+
# Opens a new channel and returns it. This method will block the calling
|
164
|
+
# thread until the response is received and the channel is guaranteed to be
|
165
|
+
# opened (this operation is very fast and inexpensive).
|
166
|
+
#
|
167
|
+
# @return [Bunny::Channel] Newly opened channel
|
129
168
|
def create_channel(n = nil)
|
130
169
|
if n && (ch = @channels[n])
|
131
170
|
ch
|
@@ -137,11 +176,12 @@ module Bunny
|
|
137
176
|
end
|
138
177
|
alias channel create_channel
|
139
178
|
|
179
|
+
# Closes the connection. This involves closing all of its channels.
|
140
180
|
def close
|
141
181
|
if @transport.open?
|
142
182
|
close_all_channels
|
143
183
|
|
144
|
-
Bunny::Timer.timeout(@disconnect_timeout, ClientTimeout) do
|
184
|
+
Bunny::Timer.timeout(@transport.disconnect_timeout, ClientTimeout) do
|
145
185
|
self.close_connection(false)
|
146
186
|
end
|
147
187
|
end
|
@@ -166,7 +206,7 @@ module Bunny
|
|
166
206
|
end
|
167
207
|
|
168
208
|
def open?
|
169
|
-
status == :open || status == :connected
|
209
|
+
(status == :open || status == :connected) && @transport.open?
|
170
210
|
end
|
171
211
|
alias connected? open?
|
172
212
|
|
@@ -174,26 +214,32 @@ module Bunny
|
|
174
214
|
# Backwards compatibility
|
175
215
|
#
|
176
216
|
|
217
|
+
# @private
|
177
218
|
def queue(*args)
|
178
219
|
@default_channel.queue(*args)
|
179
220
|
end
|
180
221
|
|
222
|
+
# @private
|
181
223
|
def direct(*args)
|
182
224
|
@default_channel.direct(*args)
|
183
225
|
end
|
184
226
|
|
227
|
+
# @private
|
185
228
|
def fanout(*args)
|
186
229
|
@default_channel.fanout(*args)
|
187
230
|
end
|
188
231
|
|
232
|
+
# @private
|
189
233
|
def topic(*args)
|
190
234
|
@default_channel.topic(*args)
|
191
235
|
end
|
192
236
|
|
237
|
+
# @private
|
193
238
|
def headers(*args)
|
194
239
|
@default_channel.headers(*args)
|
195
240
|
end
|
196
241
|
|
242
|
+
# @private
|
197
243
|
def exchange(*args)
|
198
244
|
@default_channel.exchange(*args)
|
199
245
|
end
|
@@ -203,35 +249,38 @@ module Bunny
|
|
203
249
|
# Implementation
|
204
250
|
#
|
205
251
|
|
206
|
-
|
252
|
+
# @private
|
207
253
|
def open_channel(ch)
|
208
254
|
n = ch.number
|
209
255
|
self.register_channel(ch)
|
210
256
|
|
211
257
|
@transport.send_frame(AMQ::Protocol::Channel::Open.encode(n, AMQ::Protocol::EMPTY_STRING))
|
212
|
-
@last_channel_open_ok =
|
258
|
+
@last_channel_open_ok = wait_on_continuations
|
213
259
|
raise_if_continuation_resulted_in_a_connection_error!
|
214
260
|
|
215
261
|
@last_channel_open_ok
|
216
262
|
end
|
217
263
|
|
264
|
+
# @private
|
218
265
|
def close_channel(ch)
|
219
266
|
n = ch.number
|
220
267
|
|
221
268
|
@transport.send_frame(AMQ::Protocol::Channel::Close.encode(n, 200, "Goodbye", 0, 0))
|
222
|
-
@last_channel_close_ok =
|
269
|
+
@last_channel_close_ok = wait_on_continuations
|
223
270
|
raise_if_continuation_resulted_in_a_connection_error!
|
224
271
|
|
225
272
|
self.unregister_channel(ch)
|
226
273
|
@last_channel_close_ok
|
227
274
|
end
|
228
275
|
|
276
|
+
# @private
|
229
277
|
def close_all_channels
|
230
278
|
@channels.reject {|n, ch| n == 0 || !ch.open? }.each do |_, ch|
|
231
|
-
Bunny::Timer.timeout(@disconnect_timeout, ClientTimeout) { ch.close }
|
279
|
+
Bunny::Timer.timeout(@transport.disconnect_timeout, ClientTimeout) { ch.close }
|
232
280
|
end
|
233
281
|
end
|
234
282
|
|
283
|
+
# @private
|
235
284
|
def close_connection(sync = true)
|
236
285
|
@transport.send_frame(AMQ::Protocol::Connection::Close.encode(200, "Goodbye", 0, 0))
|
237
286
|
|
@@ -239,10 +288,11 @@ module Bunny
|
|
239
288
|
@status = :not_connected
|
240
289
|
|
241
290
|
if sync
|
242
|
-
@last_connection_close_ok =
|
291
|
+
@last_connection_close_ok = wait_on_continuations
|
243
292
|
end
|
244
293
|
end
|
245
294
|
|
295
|
+
# @private
|
246
296
|
def handle_frame(ch_number, method)
|
247
297
|
# puts "Session#handle_frame on #{ch_number}: #{method.inspect}"
|
248
298
|
case method
|
@@ -252,17 +302,17 @@ module Bunny
|
|
252
302
|
@continuations.push(method)
|
253
303
|
when AMQ::Protocol::Connection::Close then
|
254
304
|
@last_connection_error = instantiate_connection_level_exception(method)
|
255
|
-
@
|
305
|
+
@continuations.push(method)
|
256
306
|
when AMQ::Protocol::Connection::CloseOk then
|
257
307
|
@last_connection_close_ok = method
|
258
308
|
begin
|
259
309
|
@continuations.clear
|
260
310
|
|
261
|
-
|
311
|
+
event_loop.stop
|
262
312
|
@event_loop = nil
|
263
313
|
|
264
314
|
@transport.close
|
265
|
-
rescue
|
315
|
+
rescue StandardError => e
|
266
316
|
puts e.class.name
|
267
317
|
puts e.message
|
268
318
|
puts e.backtrace
|
@@ -280,10 +330,15 @@ module Bunny
|
|
280
330
|
when AMQ::Protocol::Basic::GetEmpty then
|
281
331
|
@channels[ch_number].handle_basic_get_empty(method)
|
282
332
|
else
|
283
|
-
@channels[ch_number]
|
333
|
+
if ch = @channels[ch_number]
|
334
|
+
ch.handle_method(method)
|
335
|
+
else
|
336
|
+
# TODO: log a warning
|
337
|
+
end
|
284
338
|
end
|
285
339
|
end
|
286
340
|
|
341
|
+
# @private
|
287
342
|
def raise_if_continuation_resulted_in_a_connection_error!
|
288
343
|
raise @last_connection_error if @last_connection_error
|
289
344
|
end
|
@@ -303,7 +358,10 @@ module Bunny
|
|
303
358
|
end
|
304
359
|
end
|
305
360
|
|
361
|
+
# @private
|
306
362
|
def handle_network_failure(exception)
|
363
|
+
raise NetworkErrorWrapper.new(exception) unless @threaded
|
364
|
+
|
307
365
|
if !recovering_from_network_failure?
|
308
366
|
@recovering_from_network_failure = true
|
309
367
|
if recoverable_network_failure?(exception)
|
@@ -320,15 +378,18 @@ module Bunny
|
|
320
378
|
end
|
321
379
|
end
|
322
380
|
|
381
|
+
# @private
|
323
382
|
def recoverable_network_failure?(exception)
|
324
383
|
# TODO: investigate if we can be a bit smarter here. MK.
|
325
384
|
true
|
326
385
|
end
|
327
386
|
|
387
|
+
# @private
|
328
388
|
def recovering_from_network_failure?
|
329
389
|
@recovering_from_network_failure
|
330
390
|
end
|
331
391
|
|
392
|
+
# @private
|
332
393
|
def recover_from_network_failure
|
333
394
|
begin
|
334
395
|
# puts "About to start recovery..."
|
@@ -346,6 +407,7 @@ module Bunny
|
|
346
407
|
end
|
347
408
|
end
|
348
409
|
|
410
|
+
# @private
|
349
411
|
def recover_channels
|
350
412
|
# default channel is reopened right after connection
|
351
413
|
# negotiation is completed, so make sure we do not try to open
|
@@ -357,10 +419,12 @@ module Bunny
|
|
357
419
|
end
|
358
420
|
end
|
359
421
|
|
422
|
+
# @private
|
360
423
|
def send_raw(*args)
|
361
424
|
@transport.write(*args)
|
362
425
|
end
|
363
426
|
|
427
|
+
# @private
|
364
428
|
def instantiate_connection_level_exception(frame)
|
365
429
|
case frame
|
366
430
|
when AMQ::Protocol::Connection::Close then
|
@@ -379,10 +443,12 @@ module Bunny
|
|
379
443
|
end
|
380
444
|
end
|
381
445
|
|
446
|
+
# @private
|
382
447
|
def hostname_from(options)
|
383
448
|
options[:host] || options[:hostname] || DEFAULT_HOST
|
384
449
|
end
|
385
450
|
|
451
|
+
# @private
|
386
452
|
def port_from(options)
|
387
453
|
fallback = if options[:tls] || options[:ssl]
|
388
454
|
AMQ::Protocol::TLS_PORT
|
@@ -393,36 +459,44 @@ module Bunny
|
|
393
459
|
options.fetch(:port, fallback)
|
394
460
|
end
|
395
461
|
|
462
|
+
# @private
|
396
463
|
def vhost_from(options)
|
397
464
|
options[:virtual_host] || options[:vhost] || DEFAULT_VHOST
|
398
465
|
end
|
399
466
|
|
467
|
+
# @private
|
400
468
|
def username_from(options)
|
401
469
|
options[:username] || options[:user] || DEFAULT_USER
|
402
470
|
end
|
403
471
|
|
472
|
+
# @private
|
404
473
|
def password_from(options)
|
405
474
|
options[:password] || options[:pass] || options [:pwd] || DEFAULT_PASSWORD
|
406
475
|
end
|
407
476
|
|
477
|
+
# @private
|
408
478
|
def heartbeat_from(options)
|
409
479
|
options[:heartbeat] || options[:heartbeat_interval] || options[:requested_heartbeat] || DEFAULT_HEARTBEAT
|
410
480
|
end
|
411
481
|
|
482
|
+
# @private
|
412
483
|
def next_channel_id
|
413
484
|
@channel_id_allocator.next_channel_id
|
414
485
|
end
|
415
486
|
|
487
|
+
# @private
|
416
488
|
def release_channel_id(i)
|
417
489
|
@channel_id_allocator.release_channel_id(i)
|
418
490
|
end
|
419
491
|
|
492
|
+
# @private
|
420
493
|
def register_channel(ch)
|
421
494
|
@channel_mutex.synchronize do
|
422
495
|
@channels[ch.number] = ch
|
423
496
|
end
|
424
497
|
end
|
425
498
|
|
499
|
+
# @private
|
426
500
|
def unregister_channel(ch)
|
427
501
|
@channel_mutex.synchronize do
|
428
502
|
n = ch.number
|
@@ -432,11 +506,17 @@ module Bunny
|
|
432
506
|
end
|
433
507
|
end
|
434
508
|
|
509
|
+
# @private
|
435
510
|
def start_main_loop
|
436
|
-
|
437
|
-
@event_loop.start
|
511
|
+
event_loop.start
|
438
512
|
end
|
439
513
|
|
514
|
+
# @private
|
515
|
+
def event_loop
|
516
|
+
@event_loop ||= MainLoop.new(@transport, self)
|
517
|
+
end
|
518
|
+
|
519
|
+
# @private
|
440
520
|
def signal_activity!
|
441
521
|
@heartbeat_sender.signal_activity! if @heartbeat_sender
|
442
522
|
end
|
@@ -458,7 +538,7 @@ module Bunny
|
|
458
538
|
# Sends multiple frames, one by one. For thread safety this method takes a channel
|
459
539
|
# object and synchronizes on it.
|
460
540
|
#
|
461
|
-
# @api
|
541
|
+
# @api private
|
462
542
|
def send_frameset(frames, channel)
|
463
543
|
# some developers end up sharing channels between threads and when multiple
|
464
544
|
# threads publish on the same channel aggressively, at some point frames will be
|
@@ -473,6 +553,7 @@ module Bunny
|
|
473
553
|
|
474
554
|
protected
|
475
555
|
|
556
|
+
# @api private
|
476
557
|
def init_connection
|
477
558
|
self.send_preamble
|
478
559
|
|
@@ -487,6 +568,7 @@ module Bunny
|
|
487
568
|
@status = :connected
|
488
569
|
end
|
489
570
|
|
571
|
+
# @api private
|
490
572
|
def open_connection
|
491
573
|
@transport.send_frame(AMQ::Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale))
|
492
574
|
|
@@ -539,6 +621,7 @@ module Bunny
|
|
539
621
|
raise "could not open connection: server did not respond with connection.open-ok" unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk)
|
540
622
|
end
|
541
623
|
|
624
|
+
# @api private
|
542
625
|
def negotiate_value(client_value, server_value)
|
543
626
|
if client_value == 0 || server_value == 0
|
544
627
|
[client_value, server_value].max
|
@@ -547,37 +630,48 @@ module Bunny
|
|
547
630
|
end
|
548
631
|
end
|
549
632
|
|
633
|
+
# @api private
|
550
634
|
def initialize_heartbeat_sender
|
551
635
|
# puts "Initializing heartbeat sender..."
|
552
636
|
@heartbeat_sender = HeartbeatSender.new(@transport)
|
553
637
|
@heartbeat_sender.start(@heartbeat)
|
554
638
|
end
|
555
639
|
|
640
|
+
# @api private
|
556
641
|
def maybe_shutdown_heartbeat_sender
|
557
642
|
@heartbeat_sender.stop if @heartbeat_sender
|
558
643
|
end
|
559
644
|
|
560
|
-
|
645
|
+
# @api private
|
561
646
|
def initialize_transport
|
562
647
|
@transport = Transport.new(self, @host, @port, @opts)
|
563
648
|
end
|
564
649
|
|
565
650
|
# Sends AMQ protocol header (also known as preamble).
|
651
|
+
# @api private
|
566
652
|
def send_preamble
|
567
653
|
@transport.send_raw(AMQ::Protocol::PREAMBLE)
|
568
654
|
end
|
569
655
|
|
570
656
|
|
571
|
-
|
572
|
-
|
573
|
-
# @api plugin
|
657
|
+
# @api private
|
574
658
|
def encode_credentials(username, password)
|
575
659
|
@credentials_encoder.encode_credentials(username, password)
|
576
660
|
end # encode_credentials(username, password)
|
577
661
|
|
662
|
+
# @api private
|
578
663
|
def credentials_encoder_for(mechanism)
|
579
664
|
Authentication::CredentialsEncoder.for_session(self)
|
580
665
|
end
|
666
|
+
|
667
|
+
# @api private
|
668
|
+
def wait_on_continuations
|
669
|
+
unless @threaded
|
670
|
+
event_loop.run_once until @continuations.length > 0
|
671
|
+
end
|
672
|
+
|
673
|
+
@continuations.pop
|
674
|
+
end
|
581
675
|
end # Session
|
582
676
|
|
583
677
|
# backwards compatibility
|