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.
@@ -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