amq-client 0.7.0.alpha35 → 0.8.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.
Files changed (69) hide show
  1. data/.rspec +0 -1
  2. data/.travis.yml +9 -3
  3. data/Gemfile +22 -12
  4. data/amq-client.gemspec +1 -1
  5. data/examples/coolio_adapter/example_helper.rb +2 -0
  6. data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +3 -3
  7. data/examples/eventmachine_adapter/{connection_failure_callback.rb → error_handling/connection_failure_callback.rb} +4 -8
  8. data/examples/eventmachine_adapter/{connection_failure_exception.rb → error_handling/connection_failure_exception.rb} +5 -9
  9. data/examples/eventmachine_adapter/{connection_loss_handler.rb → error_handling/connection_loss_handler_that_fails_over.rb} +12 -12
  10. data/examples/eventmachine_adapter/error_handling/connection_loss_handler_with_automatic_recovery.rb +85 -0
  11. data/examples/eventmachine_adapter/error_handling/connection_loss_handler_with_manual_recovery.rb +85 -0
  12. data/examples/eventmachine_adapter/error_handling/handling_a_channel_level_exception.rb +2 -5
  13. data/examples/eventmachine_adapter/example_helper.rb +2 -0
  14. data/examples/eventmachine_adapter/server_capabilities.rb +12 -0
  15. data/examples/eventmachine_adapter/tls/tls_without_peer_verification.rb +2 -2
  16. data/lib/amq/client/async/adapter.rb +170 -31
  17. data/lib/amq/client/async/adapters/coolio.rb +18 -1
  18. data/lib/amq/client/async/adapters/event_machine.rb +48 -32
  19. data/lib/amq/client/async/adapters/eventmachine.rb +3 -1
  20. data/lib/amq/client/async/callbacks.rb +9 -7
  21. data/lib/amq/client/async/channel.rb +113 -20
  22. data/lib/amq/client/async/consumer.rb +270 -0
  23. data/lib/amq/client/async/exchange.rb +137 -16
  24. data/lib/amq/client/async/extensions/rabbitmq/confirm.rb +4 -4
  25. data/lib/amq/client/async/queue.rb +217 -113
  26. data/lib/amq/client/callbacks.rb +2 -0
  27. data/lib/amq/client/consumer_tag_generator.rb +24 -0
  28. data/lib/amq/client/exceptions.rb +10 -6
  29. data/lib/amq/client/handlers_registry.rb +2 -0
  30. data/lib/amq/client/queue.rb +2 -0
  31. data/lib/amq/client/server_named_entity.rb +1 -8
  32. data/lib/amq/client/settings.rb +64 -2
  33. data/lib/amq/client/version.rb +3 -1
  34. data/spec/benchmarks/adapters.rb +2 -0
  35. data/spec/client/framing/io_frame_spec.rb +9 -6
  36. data/spec/integration/coolio/basic_ack_spec.rb +2 -0
  37. data/spec/integration/coolio/basic_cancel_spec.rb +2 -0
  38. data/spec/integration/coolio/basic_consume_spec.rb +58 -0
  39. data/spec/integration/coolio/basic_get_spec.rb +2 -0
  40. data/spec/integration/coolio/basic_return_spec.rb +2 -0
  41. data/spec/integration/coolio/channel_close_spec.rb +2 -0
  42. data/spec/integration/coolio/channel_flow_spec.rb +2 -0
  43. data/spec/integration/coolio/connection_close_spec.rb +2 -0
  44. data/spec/integration/coolio/connection_start_spec.rb +2 -0
  45. data/spec/integration/coolio/exchange_declare_spec.rb +8 -6
  46. data/spec/integration/coolio/spec_helper.rb +2 -0
  47. data/spec/integration/coolio/tx_commit_spec.rb +2 -1
  48. data/spec/integration/coolio/tx_rollback_spec.rb +1 -1
  49. data/spec/integration/eventmachine/basic_ack_spec.rb +3 -1
  50. data/spec/integration/eventmachine/basic_cancel_spec.rb +2 -0
  51. data/spec/integration/eventmachine/basic_consume_spec.rb +90 -6
  52. data/spec/integration/eventmachine/basic_get_spec.rb +2 -0
  53. data/spec/integration/eventmachine/basic_return_spec.rb +2 -0
  54. data/spec/integration/eventmachine/channel_close_spec.rb +2 -0
  55. data/spec/integration/eventmachine/channel_flow_spec.rb +4 -2
  56. data/spec/integration/eventmachine/concurrent_basic_publish_spec.rb +79 -0
  57. data/spec/integration/eventmachine/connection_close_spec.rb +2 -0
  58. data/spec/integration/eventmachine/connection_start_spec.rb +2 -0
  59. data/spec/integration/eventmachine/exchange_declare_spec.rb +4 -2
  60. data/spec/integration/eventmachine/queue_declare_spec.rb +2 -0
  61. data/spec/integration/eventmachine/regressions/amqp_gem_issue66_spec.rb +2 -0
  62. data/spec/integration/eventmachine/spec_helper.rb +2 -0
  63. data/spec/integration/eventmachine/tx_commit_spec.rb +2 -1
  64. data/spec/integration/eventmachine/tx_rollback_spec.rb +1 -1
  65. data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +2 -0
  66. data/spec/spec_helper.rb +10 -0
  67. data/spec/unit/client/settings_spec.rb +92 -3
  68. metadata +24 -23
  69. data/CONTRIBUTORS +0 -3
@@ -100,7 +100,7 @@ module AMQ
100
100
  # AMQ protocol defines two-step process of closing connection (send Connection.Close
101
101
  # to the peer and wait for Connection.Close-Ok), implemented by {Adapter#disconnect}
102
102
  def close_connection
103
- raise MissingInterfaceMethodError.new("AMQ::Client.close_connection")
103
+ raise NotImplementedError
104
104
  end unless defined?(:close_connection) # since it is a module, this method may already be defined
105
105
  end
106
106
  end # self.included(host)
@@ -196,11 +196,12 @@ module AMQ
196
196
  #
197
197
 
198
198
 
199
+
199
200
  # Establish socket connection to the server.
200
201
  #
201
202
  # @api plugin
202
203
  def establish_connection(settings)
203
- raise MissingInterfaceMethodError.new("AMQ::Client#establish_connection(settings)")
204
+ raise NotImplementedError
204
205
  end
205
206
 
206
207
  # Properly close connection with AMQ broker, as described in
@@ -224,6 +225,8 @@ module AMQ
224
225
  end
225
226
 
226
227
 
228
+
229
+
227
230
  # Sends AMQ protocol header (also known as preamble).
228
231
  #
229
232
  # @note This must be implemented by all AMQP clients.
@@ -244,11 +247,19 @@ module AMQ
244
247
  end
245
248
  end
246
249
 
247
- # Sends multiple frames, one by one.
250
+ # Sends multiple frames, one by one. For thread safety this method takes a channel
251
+ # object and synchronizes on it.
248
252
  #
249
253
  # @api public
250
- def send_frameset(frames)
251
- frames.each { |frame| self.send_frame(frame) }
254
+ def send_frameset(frames, channel)
255
+ # some (many) developers end up sharing channels between threads and when multiple
256
+ # threads publish on the same channel aggressively, at some point frames will be
257
+ # delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
258
+ # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
259
+ # locking. Note that "single frame" methods do not need this kind of synchronization. MK.
260
+ channel.synchronize do
261
+ frames.each { |frame| self.send_frame(frame) }
262
+ end
252
263
  end # send_frameset(frames)
253
264
 
254
265
 
@@ -274,18 +285,166 @@ module AMQ
274
285
  end # vhost
275
286
 
276
287
 
288
+
289
+ # @group Error Handling and Recovery
290
+
291
+ # Called when initial TCP connection fails.
292
+ # @api public
293
+ def tcp_connection_failed
294
+ @recovered = false
295
+
296
+ @on_tcp_connection_failure.call(@settings) if @on_tcp_connection_failure
297
+ end
298
+
277
299
  # Called when previously established TCP connection fails.
278
300
  # @api public
279
301
  def tcp_connection_lost
302
+ @recovered = false
303
+
280
304
  @on_tcp_connection_loss.call(self, @settings) if @on_tcp_connection_loss
305
+ self.handle_connection_interruption
281
306
  end
282
307
 
283
- # Called when initial TCP connection fails.
308
+ # @return [Boolean]
309
+ def reconnecting?
310
+ @reconnecting
311
+ end # reconnecting?
312
+
313
+
314
+ # Defines a callback that will be run when initial TCP connection fails.
315
+ # You can define only one callback.
316
+ #
284
317
  # @api public
285
- def tcp_connection_failed
286
- @on_tcp_connection_failure.call(@settings) if @on_tcp_connection_failure
318
+ def on_tcp_connection_failure(&block)
319
+ @on_tcp_connection_failure = block
287
320
  end
288
321
 
322
+ # Defines a callback that will be run when TCP connection to AMQP broker is lost (interrupted).
323
+ # You can define only one callback.
324
+ #
325
+ # @api public
326
+ def on_tcp_connection_loss(&block)
327
+ @on_tcp_connection_loss = block
328
+ end
329
+
330
+ # Defines a callback that will be run when TCP connection is closed before authentication
331
+ # finishes. Usually this means authentication failure. You can define only one callback.
332
+ #
333
+ # @api public
334
+ def on_possible_authentication_failure(&block)
335
+ @on_possible_authentication_failure = block
336
+ end
337
+
338
+
339
+ # Defines a callback that will be executed when connection is closed after
340
+ # connection-level exception. Only one callback can be defined (the one defined last
341
+ # replaces previously added ones).
342
+ #
343
+ # @api public
344
+ def on_error(&block)
345
+ self.redefine_callback(:error, &block)
346
+ end
347
+
348
+
349
+ # Defines a callback that will be executed after TCP connection is interrupted (typically because of a network failure).
350
+ # Only one callback can be defined (the one defined last replaces previously added ones).
351
+ #
352
+ # @api public
353
+ def on_connection_interruption(&block)
354
+ self.redefine_callback(:after_connection_interruption, &block)
355
+ end # on_connection_interruption(&block)
356
+ alias after_connection_interruption on_connection_interruption
357
+
358
+
359
+ # @private
360
+ # @api plugin
361
+ def handle_connection_interruption
362
+ @channels.each { |n, c| c.handle_connection_interruption }
363
+ self.exec_callback_yielding_self(:after_connection_interruption)
364
+ end # handle_connection_interruption
365
+
366
+
367
+
368
+ # Defines a callback that will be executed after TCP connection has recovered after a network failure
369
+ # but before AMQP connection is re-opened.
370
+ # Only one callback can be defined (the one defined last replaces previously added ones).
371
+ #
372
+ # @api public
373
+ def before_recovery(&block)
374
+ self.redefine_callback(:before_recovery, &block)
375
+ end # before_recovery(&block)
376
+
377
+ # @private
378
+ def run_before_recovery_callbacks
379
+ self.exec_callback_yielding_self(:before_recovery, @settings)
380
+
381
+ @channels.each { |n, ch| ch.run_before_recovery_callbacks }
382
+ end
383
+
384
+
385
+ # Defines a callback that will be executed after AMQP connection has recovered after a network failure..
386
+ # Only one callback can be defined (the one defined last replaces previously added ones).
387
+ #
388
+ # @api public
389
+ def on_recovery(&block)
390
+ self.redefine_callback(:after_recovery, &block)
391
+ end # on_recovery(&block)
392
+ alias after_recovery on_recovery
393
+
394
+ # @private
395
+ def run_after_recovery_callbacks
396
+ self.exec_callback_yielding_self(:after_recovery, @settings)
397
+
398
+ @channels.each { |n, ch| ch.run_after_recovery_callbacks }
399
+ end
400
+
401
+
402
+ # @return [Boolean] whether connection is in the automatic recovery mode
403
+ # @api public
404
+ def auto_recovering?
405
+ !!@auto_recovery
406
+ end # auto_recovering?
407
+ alias auto_recovery? auto_recovering?
408
+
409
+
410
+ # Performs recovery of channels that are in the automatic recovery mode. Does not run recovery
411
+ # callbacks.
412
+ #
413
+ # @see Channel#auto_recover
414
+ # @see Queue#auto_recover
415
+ # @see Exchange#auto_recover
416
+ # @api plugin
417
+ def auto_recover
418
+ @channels.select { |channel_id, ch| ch.auto_recovering? }.each { |n, ch| ch.auto_recover }
419
+ end # auto_recover
420
+
421
+
422
+ # Performs recovery of channels that are in the automatic recovery mode. "before recovery" callbacks
423
+ # are run immediately, "after recovery" callbacks are run after AMQP connection is re-established and
424
+ # auto recovery is performed (using #auto_recover).
425
+ #
426
+ # Use this method if you want to run automatic recovery process after handling a connection-level exception,
427
+ # for example, 320 CONNECTION_FORCED (used by RabbitMQ when it is shut down gracefully).
428
+ #
429
+ # @see Channel#auto_recover
430
+ # @see Queue#auto_recover
431
+ # @see Exchange#auto_recover
432
+ # @api plugin
433
+ def start_automatic_recovery
434
+ self.run_before_recovery_callbacks
435
+ self.register_connection_callback do
436
+ # always run automatic recovery, because it is per-channel
437
+ # and connection has to start it. Channels that did not opt-in for
438
+ # autorecovery won't be selected. MK.
439
+ self.auto_recover
440
+ self.run_after_recovery_callbacks
441
+ end
442
+ end # start_automatic_recovery
443
+
444
+
445
+ # @endgroup
446
+
447
+
289
448
 
290
449
 
291
450
  #
@@ -298,7 +457,7 @@ module AMQ
298
457
  # @note This must be implemented by all AMQP clients.
299
458
  # @api plugin
300
459
  def send_raw(data)
301
- raise MissingInterfaceMethodError.new("AMQ::Client#send_raw(data)")
460
+ raise NotImplementedError
302
461
  end
303
462
 
304
463
  # Sends connection preamble to the broker.
@@ -358,7 +517,8 @@ module AMQ
358
517
  @last_server_heartbeat = Time.now
359
518
  else
360
519
  if callable = AMQ::Client::HandlersRegistry.find(frame.method_class)
361
- callable.call(self, frames.first, frames[1..-1])
520
+ f = frames.shift
521
+ callable.call(self, f, frames)
362
522
  else
363
523
  raise MissingHandlerError.new(frames.first)
364
524
  end
@@ -380,20 +540,6 @@ module AMQ
380
540
 
381
541
 
382
542
 
383
- # @group Error handling
384
-
385
- # Defines a callback that will be executed when channel is closed after
386
- # channel-level exception. Only one callback can be defined (the one defined last
387
- # replaces previously added ones).
388
- #
389
- # @api public
390
- def on_error(&block)
391
- self.redefine_callback(:error, &block)
392
- end
393
-
394
- # @endgroup
395
-
396
-
397
543
 
398
544
 
399
545
  # Handles connection.start.
@@ -449,8 +595,6 @@ module AMQ
449
595
  # @api plugin
450
596
  # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.5.2.9)
451
597
  def handle_close(conn_close)
452
- self.handle_connection_interruption
453
-
454
598
  closed!
455
599
  # TODO: use proper exception class, provide protocol class (we know conn_close.class_id and conn_close.method_id) as well!
456
600
  self.exec_callback_yielding_self(:error, conn_close)
@@ -466,11 +610,6 @@ module AMQ
466
610
  self.disconnection_successful
467
611
  end # handle_close_ok(close_ok)
468
612
 
469
- # @api plugin
470
- def handle_connection_interruption
471
- @channels.each { |n, c| c.handle_connection_interruption }
472
- end # handle_connection_interruption
473
-
474
613
 
475
614
 
476
615
  protected
@@ -122,6 +122,8 @@ module AMQ
122
122
  @locale = @settings.fetch(:locale, "en_GB")
123
123
  @client_properties = Settings.client_properties.merge(@settings.fetch(:client_properties, Hash.new))
124
124
 
125
+ @auto_recovery = (!!@settings[:auto_recovery])
126
+
125
127
  socket
126
128
  end
127
129
 
@@ -276,6 +278,21 @@ module AMQ
276
278
 
277
279
  # @api private
278
280
  def post_init
281
+ if @had_successfully_connected_before
282
+ @recovered = true
283
+
284
+ self.exec_callback_yielding_self(:before_recovery, @settings)
285
+
286
+ self.register_connection_callback do
287
+ self.auto_recover
288
+ self.exec_callback_yielding_self(:after_recovery, @settings)
289
+ end
290
+ end
291
+
292
+ # now we can set it. MK.
293
+ @had_successfully_connected_before = true
294
+ @reconnecting = false
295
+
279
296
  self.reset
280
297
  self.handshake
281
298
  end
@@ -285,7 +302,7 @@ module AMQ
285
302
  @chunk_buffer = ""
286
303
  @frames = Array.new
287
304
  end
288
- end # CoolioClient
305
+ end # CoolioClient
289
306
  end # Async
290
307
  end # Client
291
308
  end # AMQ
@@ -21,6 +21,8 @@ module AMQ
21
21
  # API
22
22
  #
23
23
 
24
+ # @group Connection operations
25
+
24
26
  # Initiates connection to AMQP broker. If callback is given, runs it when (and if) AMQP connection
25
27
  # succeeds.
26
28
  #
@@ -60,15 +62,42 @@ module AMQ
60
62
 
61
63
  if !@reconnecting
62
64
  @reconnecting = true
65
+ self.reset
66
+ end
67
+
68
+ EventMachine.reconnect(@settings[:host], @settings[:port], self)
69
+ end
63
70
 
64
- self.handle_connection_interruption
71
+ # Similar to #reconnect, but uses different connection settings
72
+ # @see #reconnect
73
+ # @api public
74
+ def reconnect_to(settings, period = 5)
75
+ if !@reconnecting
76
+ @reconnecting = true
65
77
  self.reset
66
78
  end
67
79
 
80
+ @settings = Settings.configure(settings)
68
81
  EventMachine.reconnect(@settings[:host], @settings[:port], self)
69
82
  end
70
83
 
71
84
 
85
+ # Periodically try to reconnect.
86
+ #
87
+ # @param [Fixnum] period Period of time, in seconds, to wait before reconnection attempt.
88
+ # @param [Boolean] force If true, enforces immediate reconnection.
89
+ # @api public
90
+ def periodically_reconnect(period = 5)
91
+ @reconnecting = true
92
+ self.reset
93
+
94
+ @periodic_reconnection_timer = EventMachine::PeriodicTimer.new(period) {
95
+ EventMachine.reconnect(@settings[:host], @settings[:port], self)
96
+ }
97
+ end
98
+
99
+ # @endgroup
100
+
72
101
 
73
102
 
74
103
 
@@ -92,30 +121,6 @@ module AMQ
92
121
  end # on_closed(&block)
93
122
  alias on_disconnection on_closed
94
123
 
95
- # Defines a callback that will be run when initial TCP connection fails.
96
- # You can define only one callback.
97
- #
98
- # @api public
99
- def on_tcp_connection_failure(&block)
100
- @on_tcp_connection_failure = block
101
- end
102
-
103
- # Defines a callback that will be run when TCP connection to AMQP broker is lost (interrupted).
104
- # You can define only one callback.
105
- #
106
- # @api public
107
- def on_tcp_connection_loss(&block)
108
- @on_tcp_connection_loss = block
109
- end
110
-
111
- # Defines a callback that will be run when TCP connection is closed before authentication
112
- # finishes. Usually this means authentication failure. You can define only one callback.
113
- #
114
- # @api public
115
- def on_possible_authentication_failure(&block)
116
- @on_possible_authentication_failure = block
117
- end
118
-
119
124
  # @see #on_open
120
125
  # @private
121
126
  def register_connection_callback(&block)
@@ -162,6 +167,8 @@ module AMQ
162
167
  @locale = @settings.fetch(:locale, "en_GB")
163
168
  @client_properties = Settings.client_properties.merge(@settings.fetch(:client_properties, Hash.new))
164
169
 
170
+ @auto_recovery = (!!@settings[:auto_recovery])
171
+
165
172
  self.reset
166
173
  self.set_pending_connect_timeout((@settings[:timeout] || 3).to_f) unless defined?(JRUBY_VERSION)
167
174
 
@@ -203,8 +210,6 @@ module AMQ
203
210
 
204
211
 
205
212
 
206
-
207
-
208
213
  #
209
214
  # Implementation
210
215
  #
@@ -243,11 +248,22 @@ module AMQ
243
248
  # software that calls #post_init before #unbind even when TCP connection
244
249
  # fails. MK.
245
250
  @tcp_connection_established = true
251
+ @periodic_reconnection_timer.cancel if @periodic_reconnection_timer
252
+
253
+
246
254
  # again, this is because #unbind is called in different situations
247
255
  # and there is no easy way to tell initial connection failure
248
256
  # from connection loss. Not in EventMachine 0.12.x, anyway. MK.
249
- @had_successfull_connected_before = true
250
257
 
258
+ if @had_successfully_connected_before
259
+ @recovered = true
260
+
261
+
262
+ self.start_automatic_recovery
263
+ end
264
+
265
+ # now we can set it. MK.
266
+ @had_successfully_connected_before = true
251
267
  @reconnecting = false
252
268
 
253
269
  self.handshake
@@ -268,7 +284,7 @@ module AMQ
268
284
  # * Initial TCP connection fails
269
285
  # @private
270
286
  def unbind(exception = nil)
271
- if !@tcp_connection_established && !@had_successfull_connected_before && !@intentionally_closing_connection
287
+ if !@tcp_connection_established && !@had_successfully_connected_before && !@intentionally_closing_connection
272
288
  @tcp_connection_failed = true
273
289
  self.tcp_connection_failed
274
290
  end
@@ -276,13 +292,13 @@ module AMQ
276
292
  closing!
277
293
  @tcp_connection_established = false
278
294
 
279
- self.handle_connection_interruption
295
+ self.handle_connection_interruption if @reconnecting
280
296
  @disconnection_deferrable.succeed
281
297
 
282
298
  closed!
283
299
 
284
300
 
285
- self.tcp_connection_lost if !@intentionally_closing_connection && @had_successfull_connected_before
301
+ self.tcp_connection_lost if !@intentionally_closing_connection && @had_successfully_connected_before
286
302
 
287
303
  # since AMQP spec dictates that authentication failure is a protocol exception
288
304
  # and protocol exceptions result in connection closure, check whether we are
@@ -386,7 +402,7 @@ module AMQ
386
402
  start_tls
387
403
  end
388
404
  end # upgrade_to_tls_if_necessary
389
- end # EventMachineClient
405
+ end # EventMachineClient
390
406
  end # Async
391
407
  end # Client
392
408
  end # AMQ