bunny 0.9.0.pre6 → 0.9.0.pre7

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.
@@ -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. MK.
30
+ # Default heartbeat interval, the same value as RabbitMQ 3.0 uses.
25
31
  DEFAULT_HEARTBEAT = 600
26
- # 128K
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://github.com/ruby-amqp/bunny",
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 = @continuations.pop
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 = @continuations.pop
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 = @continuations.pop
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
- @contunuations.push(method)
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
- @event_loop.stop
311
+ event_loop.stop
262
312
  @event_loop = nil
263
313
 
264
314
  @transport.close
265
- rescue Exception => e
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].handle_method(method)
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
- @event_loop = MainLoop.new(@transport, self)
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 public
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