amq-client 0.7.0.alpha35 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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