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
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require "amq/client/exceptions"
3
4
  require "amq/client/entity"
4
5
  require "amq/client/server_named_entity"
5
6
 
@@ -13,13 +14,8 @@ module AMQ
13
14
  include ServerNamedEntity
14
15
  extend ProtocolMethodHandlers
15
16
 
16
- TYPES = [:fanout, :direct, :topic, :headers].freeze
17
+ BUILTIN_TYPES = [:fanout, :direct, :topic, :headers].freeze
17
18
 
18
- class IncompatibleExchangeTypeError < StandardError
19
- def initialize(types, given)
20
- super("#{given.inspect} exchange type is unknown. Standard types are #{TYPES.inspect}, custom exchange types must begin with x-, for example: x-recent-history")
21
- end
22
- end
23
19
 
24
20
 
25
21
  #
@@ -30,45 +26,81 @@ module AMQ
30
26
  attr_reader :channel
31
27
 
32
28
  # Exchange name. May be server-generated or assigned directly.
29
+ # @return [String]
33
30
  attr_reader :name
34
31
 
35
32
  # @return [Symbol] One of :direct, :fanout, :topic, :headers
36
33
  attr_reader :type
37
34
 
35
+ # @return [Hash] Additional arguments given on queue declaration. Typically used by AMQP extensions.
36
+ attr_reader :arguments
37
+
38
+
39
+
38
40
  def initialize(connection, channel, name, type = :fanout)
39
- if !(TYPES.include?(type.to_sym) || type.to_s =~ /^x-.+/i)
40
- raise IncompatibleExchangeTypeError.new(TYPES, type)
41
+ if !(BUILTIN_TYPES.include?(type.to_sym) || type.to_s =~ /^x-.+/i)
42
+ raise UnknownExchangeTypeError.new(BUILTIN_TYPES, type)
41
43
  end
42
44
 
43
- @connection = connection
44
- @channel = channel
45
- @name = name
46
- @type = type
45
+ @connection = connection
46
+ @channel = channel
47
+ @name = name
48
+ @type = type
47
49
 
48
50
  # register pre-declared exchanges
49
- if @name == AMQ::Protocol::EMPTY_STRING || @name =~ /^amq\.(fanout|topic)/
51
+ if @name == AMQ::Protocol::EMPTY_STRING || @name =~ /^amq\.(direct|fanout|topic|match|headers)/
50
52
  @channel.register_exchange(self)
51
53
  end
52
54
 
53
55
  super(connection)
54
56
  end
55
57
 
56
-
58
+ # @return [Boolean] true if this exchange is of type `fanout`
59
+ # @api public
57
60
  def fanout?
58
61
  @type == :fanout
59
62
  end
60
63
 
64
+ # @return [Boolean] true if this exchange is of type `direct`
65
+ # @api public
61
66
  def direct?
62
67
  @type == :direct
63
68
  end
64
69
 
70
+ # @return [Boolean] true if this exchange is of type `topic`
71
+ # @api public
65
72
  def topic?
66
73
  @type == :topic
67
74
  end
68
75
 
76
+ # @return [Boolean] true if this exchange is of type `headers`
77
+ # @api public
78
+ def headers?
79
+ @type == :headers
80
+ end
81
+
82
+ # @return [Boolean] true if this exchange is of a custom type (begins with x-)
83
+ # @api public
84
+ def custom_type?
85
+ @type.to_s =~ /^x-.+/i
86
+ end # custom_type?
87
+
88
+ # @return [Boolean] true if this exchange is a pre-defined one (amq.direct, amq.fanout, amq.match and so on)
89
+ def predefined?
90
+ @name && !!(@name =~ /^amq\.(direct|fanout|topic|headers|match)/i)
91
+ end # predefined?
69
92
 
70
93
 
94
+ # @group Declaration
95
+
96
+ # @api public
71
97
  def declare(passive = false, durable = false, auto_delete = false, nowait = false, arguments = nil, &block)
98
+ # for re-declaration
99
+ @passive = passive
100
+ @durable = durable
101
+ @auto_delete = auto_delete
102
+ @arguments = arguments
103
+
72
104
  @connection.send_frame(Protocol::Exchange::Declare.encode(@channel.id, @name, @type.to_s, passive, durable, auto_delete, false, nowait, arguments))
73
105
 
74
106
  unless nowait
@@ -80,6 +112,23 @@ module AMQ
80
112
  end
81
113
 
82
114
 
115
+ # @api public
116
+ def redeclare(&block)
117
+ nowait = block.nil?
118
+ @connection.send_frame(Protocol::Exchange::Declare.encode(@channel.id, @name, @type.to_s, @passive, @durable, @auto_delete, false, nowait, @arguments))
119
+
120
+ unless nowait
121
+ self.define_callback(:declare, &block)
122
+ @channel.exchanges_awaiting_declare_ok.push(self)
123
+ end
124
+
125
+ self
126
+ end # redeclare(&block)
127
+
128
+ # @endgroup
129
+
130
+
131
+ # @api public
83
132
  def delete(if_unused = false, nowait = false, &block)
84
133
  @connection.send_frame(Protocol::Exchange::Delete.encode(@channel.id, @name, if_unused, nowait))
85
134
 
@@ -94,22 +143,94 @@ module AMQ
94
143
  end # delete(if_unused = false, nowait = false)
95
144
 
96
145
 
146
+
147
+ # @group Publishing Messages
148
+
149
+ # @api public
97
150
  def publish(payload, routing_key = AMQ::Protocol::EMPTY_STRING, user_headers = {}, mandatory = false, immediate = false, frame_size = nil)
98
151
  headers = { :priority => 0, :delivery_mode => 2, :content_type => "application/octet-stream" }.merge(user_headers)
99
- @connection.send_frameset(Protocol::Basic::Publish.encode(@channel.id, payload, headers, @name, routing_key, mandatory, immediate, (frame_size || @connection.frame_max)))
152
+ @connection.send_frameset(Protocol::Basic::Publish.encode(@channel.id, payload, headers, @name, routing_key, mandatory, immediate, (frame_size || @connection.frame_max)), @channel)
100
153
 
101
154
  self
102
155
  end
103
156
 
104
157
 
158
+ # @api public
105
159
  def on_return(&block)
106
160
  self.redefine_callback(:return, &block)
107
161
 
108
162
  self
109
163
  end # on_return(&block)
110
164
 
165
+ # @endgroup
166
+
111
167
 
112
168
 
169
+ # @group Error Handling and Recovery
170
+
171
+
172
+ # Defines a callback that will be executed after TCP connection is interrupted (typically because of a network failure).
173
+ # Only one callback can be defined (the one defined last replaces previously added ones).
174
+ #
175
+ # @api public
176
+ def on_connection_interruption(&block)
177
+ self.redefine_callback(:after_connection_interruption, &block)
178
+ end # on_connection_interruption(&block)
179
+ alias after_connection_interruption on_connection_interruption
180
+
181
+ # @private
182
+ def handle_connection_interruption(method = nil)
183
+ self.exec_callback_yielding_self(:after_connection_interruption)
184
+ end # handle_connection_interruption
185
+
186
+
187
+
188
+ # Defines a callback that will be executed after TCP connection is recovered after a network failure
189
+ # but before AMQP connection is re-opened.
190
+ # Only one callback can be defined (the one defined last replaces previously added ones).
191
+ #
192
+ # @api public
193
+ def before_recovery(&block)
194
+ self.redefine_callback(:before_recovery, &block)
195
+ end # before_recovery(&block)
196
+
197
+ # @private
198
+ def run_before_recovery_callbacks
199
+ self.exec_callback_yielding_self(:before_recovery)
200
+ end
201
+
202
+
203
+ # Defines a callback that will be executed when AMQP connection is recovered after a network failure..
204
+ # Only one callback can be defined (the one defined last replaces previously added ones).
205
+ #
206
+ # @api public
207
+ def on_recovery(&block)
208
+ self.redefine_callback(:after_recovery, &block)
209
+ end # on_recovery(&block)
210
+ alias after_recovery on_recovery
211
+
212
+ # @private
213
+ def run_after_recovery_callbacks
214
+ self.exec_callback_yielding_self(:after_recovery)
215
+ end
216
+
217
+
218
+ # Called by associated connection object when AMQP connection has been re-established
219
+ # (for example, after a network failure).
220
+ #
221
+ # @api plugin
222
+ def auto_recover
223
+ self.redeclare unless predefined?
224
+ end # auto_recover
225
+
226
+ # @endgroup
227
+
228
+
229
+
230
+ #
231
+ # Implementation
232
+ #
233
+
113
234
 
114
235
  def handle_declare_ok(method)
115
236
  @name = method.exchange if self.anonymous?
@@ -151,7 +272,7 @@ module AMQ
151
272
  exchange.exec_callback(:return, method, header, body)
152
273
  end
153
274
 
154
- end # Exchange
275
+ end # Exchange
155
276
  end # Async
156
277
  end # Client
157
278
  end # AMQ
@@ -100,11 +100,11 @@ module AMQ
100
100
  # @see #confirm
101
101
  def confirm_select(nowait = false, &block)
102
102
  if nowait && block
103
- raise "You can't use Confirm.Select with nowait=true and a callback at the same time."
103
+ raise ArgumentError, "confirm.select with nowait = true and a callback makes no sense"
104
104
  end
105
105
 
106
106
  @uses_publisher_confirmations = true
107
- self.redefine_callback(:confirm_select, &block)
107
+ self.redefine_callback(:confirm_select, &block) unless nowait
108
108
  @connection.send_frame(Protocol::Confirm::Select.encode(@id, nowait))
109
109
 
110
110
  self
@@ -220,9 +220,9 @@ module AMQ
220
220
  # @see AMQ::Client::Exchange#publish
221
221
  # @see AMQ::Client::Extensions::RabbitMQ::Channel#publisher_index
222
222
  # @return [self] self
223
- def publish(*args)
223
+ def publish(*args, &block)
224
224
  super(*args)
225
- @channel.after_publish(*args)
225
+ @channel.after_publish(*args, &block)
226
226
 
227
227
  self
228
228
  end # publish
@@ -3,7 +3,9 @@
3
3
  require "amq/client/async/entity"
4
4
  require "amq/client/adapter"
5
5
  require "amq/client/server_named_entity"
6
+
6
7
  require "amq/protocol/get_response"
8
+ require "amq/client/async/consumer"
7
9
 
8
10
  module AMQ
9
11
  module Client
@@ -24,13 +26,26 @@ module AMQ
24
26
  #
25
27
 
26
28
  # Qeueue name. May be server-generated or assigned directly.
29
+ # @return [String]
27
30
  attr_reader :name
28
31
 
29
32
  # Channel this queue belongs to.
33
+ # @return [AMQ::Client::Channel]
30
34
  attr_reader :channel
31
35
 
32
- # Consumer tag identifies subscription for message delivery. It is nil for queues that are not subscribed for messages. See AMQ::Client::Queue#subscribe.
33
- attr_reader :consumer_tag
36
+ # @return [Array<Hash>] All consumers on this queue.
37
+ attr_reader :consumers
38
+
39
+ # @return [AMQ::Client::Consumer] Default consumer (registered with {Queue#consume}).
40
+ attr_reader :default_consumer
41
+
42
+ # @return [Hash] Additional arguments given on queue declaration. Typically used by AMQP extensions.
43
+ attr_reader :arguments
44
+
45
+ # @return [Array<Hash>]
46
+ attr_reader :bindings
47
+
48
+
34
49
 
35
50
  # @param [AMQ::Client::Adapter] AMQ networking adapter to use.
36
51
  # @param [AMQ::Client::Channel] AMQ channel this queue object uses.
@@ -41,18 +56,22 @@ module AMQ
41
56
 
42
57
  super(connection)
43
58
 
44
- @name = name
45
- @channel = channel
46
- end
47
-
48
- def dup
49
- if @name.empty?
50
- raise RuntimeError.new("You can't clone anonymous queue until it receives server-generated name. Move the code with #dup to the callback for the #declare method.")
59
+ @name = name
60
+ # this has to stay true even after queue.declare-ok arrives. MK.
61
+ @server_named = @name.empty?
62
+ if @server_named
63
+ self.on_connection_interruption do
64
+ # server-named queue need to get new names after recovery. MK.
65
+ @name = AMQ::Protocol::EMPTY_STRING
66
+ end
51
67
  end
52
68
 
53
- o = super
54
- o.reset_consumer_tag!
55
- o
69
+ @channel = channel
70
+
71
+ # primarily for autorecovery. MK.
72
+ @bindings = Array.new
73
+
74
+ @consumers = Hash.new
56
75
  end
57
76
 
58
77
 
@@ -75,6 +94,8 @@ module AMQ
75
94
  end # auto_delete?
76
95
 
77
96
 
97
+ # @group Declaration
98
+
78
99
  # Declares this queue.
79
100
  #
80
101
  #
@@ -85,9 +106,14 @@ module AMQ
85
106
  def declare(passive = false, durable = false, exclusive = false, auto_delete = false, nowait = false, arguments = nil, &block)
86
107
  raise ArgumentError, "declaration with nowait does not make sense for server-named queues! Either specify name other than empty string or use #declare without nowait" if nowait && self.anonymous?
87
108
 
109
+ # these two are for autorecovery. MK.
110
+ @passive = passive
111
+ @server_named = @name.empty?
112
+
88
113
  @durable = durable
89
114
  @exclusive = exclusive
90
115
  @auto_delete = auto_delete
116
+ @arguments = arguments
91
117
 
92
118
  nowait = true if !block && !@name.empty?
93
119
  @connection.send_frame(Protocol::Queue::Declare.encode(@channel.id, @name, passive, durable, exclusive, auto_delete, nowait, arguments))
@@ -100,6 +126,31 @@ module AMQ
100
126
  self
101
127
  end
102
128
 
129
+ # Re-declares queue with the same attributes
130
+ # @api public
131
+ def redeclare(&block)
132
+ nowait = true if !block && !@name.empty?
133
+
134
+ # server-named queues get their new generated names.
135
+ new_name = if @server_named
136
+ AMQ::Protocol::EMPTY_STRING
137
+ else
138
+ @name
139
+ end
140
+ @connection.send_frame(Protocol::Queue::Declare.encode(@channel.id, new_name, @passive, @durable, @exclusive, @auto_delete, false, @arguments))
141
+
142
+ if !nowait
143
+ self.append_callback(:declare, &block)
144
+ @channel.queues_awaiting_declare_ok.push(self)
145
+ end
146
+
147
+ self
148
+ end
149
+
150
+ # @endgroup
151
+
152
+
153
+
103
154
  # Deletes this queue.
104
155
  #
105
156
  # @param [Boolean] if_unused delete only if queue has no consumers (subscribers).
@@ -123,6 +174,10 @@ module AMQ
123
174
  self
124
175
  end # delete(channel, queue, if_unused, if_empty, nowait, &block)
125
176
 
177
+
178
+
179
+ # @group Binding
180
+
126
181
  #
127
182
  # @return [Queue] self
128
183
  #
@@ -141,11 +196,14 @@ module AMQ
141
196
 
142
197
  if !nowait
143
198
  self.append_callback(:bind, &block)
144
-
145
- # TODO: handle channel & connection-level exceptions
146
199
  @channel.queues_awaiting_bind_ok.push(self)
147
200
  end
148
201
 
202
+ # store bindings for automatic recovery, but BE VERY CAREFUL to
203
+ # not cause an infinite rebinding loop here when we recover. MK.
204
+ binding = { :exchange => exchange_name, :routing_key => routing_key, :arguments => arguments }
205
+ @bindings.push(binding) unless @bindings.include?(binding)
206
+
149
207
  self
150
208
  end
151
209
 
@@ -165,12 +223,34 @@ module AMQ
165
223
  @connection.send_frame(Protocol::Queue::Unbind.encode(@channel.id, @name, exchange_name, routing_key, arguments))
166
224
 
167
225
  self.append_callback(:unbind, &block)
168
- # TODO: handle channel & connection-level exceptions
169
226
  @channel.queues_awaiting_unbind_ok.push(self)
170
227
 
228
+
229
+ @bindings.delete_if { |b| b[:exchange] == exchange_name }
230
+
171
231
  self
172
232
  end
173
233
 
234
+ # Used by automatic recovery machinery.
235
+ # @private
236
+ # @api plugin
237
+ def rebind(&block)
238
+ @bindings.each { |b| self.bind(b[:exchange], b[:routing_key], true, b[:arguments]) }
239
+ end
240
+
241
+ # @endgroup
242
+
243
+
244
+
245
+
246
+ # @group Consuming messages
247
+
248
+ # @return [Class] AMQ::Client::Consumer or other class implementing consumer API. Used by libraries like {https://github.com/ruby-amqp/amqp Ruby amqp gem}.
249
+ # @api plugin
250
+ def self.consumer_class
251
+ AMQ::Client::Async::Consumer
252
+ end # self.consumer_class
253
+
174
254
 
175
255
  #
176
256
  # @return [Queue] self
@@ -178,46 +258,44 @@ module AMQ
178
258
  # @api public
179
259
  # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.3.)
180
260
  def consume(no_ack = false, exclusive = false, nowait = false, no_local = false, arguments = nil, &block)
181
- raise RuntimeError.new("This instance is already being consumed! Create another one using #dup.") if @consumer_tag
261
+ raise RuntimeError.new("This queue already has default consumer. Please instantiate AMQ::Client::Consumer directly to register additional consumers.") if @default_consumer
182
262
 
183
- nowait = true unless block
184
- @consumer_tag = generate_consumer_tag(name)
185
- @connection.send_frame(Protocol::Basic::Consume.encode(@channel.id, @name, @consumer_tag, no_local, no_ack, exclusive, nowait, arguments))
263
+ nowait = true unless block
264
+ @default_consumer = self.class.consumer_class.new(@channel, self, generate_consumer_tag(@name), exclusive, no_ack, arguments, no_local, &block)
265
+ @default_consumer.consume(nowait, &block)
186
266
 
187
- @channel.consumers[@consumer_tag] = self
267
+ self
268
+ end
188
269
 
189
- if !nowait
190
- # unlike #get, here it is reasonable to expect more than one callback
191
- # so we use #append_callback
192
- self.append_callback(:consume, &block)
270
+ # Unsubscribes from message delivery.
271
+ # @return [Queue] self
272
+ #
273
+ # @api public
274
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.5.)
275
+ def cancel(nowait = false, &block)
276
+ raise "There is no default consumer for this queue. This usually means that you are trying to unsubscribe a queue that never was subscribed for messages in the first place." if @default_consumer.nil?
193
277
 
194
- @channel.queues_awaiting_consume_ok.push(self)
195
- end
278
+ @default_consumer.cancel(nowait, &block)
196
279
 
197
280
  self
198
- end
281
+ end # cancel(&block)
199
282
 
200
- # Unique string supposed to be used as a consumer tag.
201
- #
202
- # @return [String] Unique string.
203
- # @api plugin
204
- def generate_consumer_tag(name)
205
- "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
206
- end
283
+ # @endgroup
207
284
 
208
- # Resets consumer tag by setting it to nil.
209
- # @return [String] Consumer tag this queue previously used.
210
- #
211
- # @api plugin
212
- def reset_consumer_tag!
213
- ct = @consumer_tag.dup
214
- @consumer_tag = nil
215
285
 
216
- ct
217
- end
218
286
 
219
287
 
220
- #
288
+ # @group Working With Messages
289
+
290
+
291
+ # @api public
292
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Sections 1.8.3.9)
293
+ def on_delivery(&block)
294
+ @default_consumer.on_delivery(&block)
295
+ end # on_delivery(&block)
296
+
297
+
298
+ # Fetches messages from the queue.
221
299
  # @return [Queue] self
222
300
  #
223
301
  # @api public
@@ -236,28 +314,9 @@ module AMQ
236
314
  self
237
315
  end # get(no_ack = false, &block)
238
316
 
239
- #
240
- # @return [Queue] self
241
- #
242
- # @api public
243
- # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.5.)
244
- def cancel(nowait = false, &block)
245
- raise "There is no consumer tag for this queue. This usually means that you are trying to unsubscribe a queue that never was subscribed for messages in the first place." if @consumer_tag.nil?
246
-
247
- @connection.send_frame(Protocol::Basic::Cancel.encode(@channel.id, @consumer_tag, nowait))
248
- @consumer_tag = nil
249
- self.clear_callbacks(:delivery)
250
- self.clear_callbacks(:consume)
251
317
 
252
- if !nowait
253
- self.redefine_callback(:cancel, &block)
254
- @channel.queues_awaiting_cancel_ok.push(self)
255
- end
256
318
 
257
- self
258
- end # cancel(&block)
259
-
260
- #
319
+ # Purges (removes all messagse from) the queue.
261
320
  # @return [Queue] self
262
321
  #
263
322
  # @api public
@@ -275,7 +334,13 @@ module AMQ
275
334
  self
276
335
  end # purge(nowait = false, &block)
277
336
 
278
- #
337
+ # @endgroup
338
+
339
+
340
+
341
+ # @group Acknowledging & Rejecting Messages
342
+
343
+ # Acknowledge a delivery tag.
279
344
  # @return [Queue] self
280
345
  #
281
346
  # @api public
@@ -297,22 +362,103 @@ module AMQ
297
362
  self
298
363
  end # reject(delivery_tag, requeue = true)
299
364
 
365
+ # @endgroup
366
+
367
+
300
368
 
301
369
 
370
+ # @group Error Handling & Recovery
371
+
372
+ # Defines a callback that will be executed after TCP connection is interrupted (typically because of a network failure).
373
+ # Only one callback can be defined (the one defined last replaces previously added ones).
374
+ #
302
375
  # @api public
303
- # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Sections 1.8.3.9)
304
- def on_delivery(&block)
305
- self.append_callback(:delivery, &block)
306
- end # on_delivery(&block)
376
+ def on_connection_interruption(&block)
377
+ self.redefine_callback(:after_connection_interruption, &block)
378
+ end # on_connection_interruption(&block)
379
+ alias after_connection_interruption on_connection_interruption
380
+
381
+ # @private
382
+ def handle_connection_interruption(method = nil)
383
+ @consumers.each { |tag, consumer| consumer.handle_connection_interruption(method) }
384
+
385
+ self.exec_callback_yielding_self(:after_connection_interruption)
386
+ end # handle_connection_interruption
387
+
388
+ # Defines a callback that will be executed after TCP connection is recovered after a network failure
389
+ # but before AMQP connection is re-opened.
390
+ # Only one callback can be defined (the one defined last replaces previously added ones).
391
+ #
392
+ # @api public
393
+ def before_recovery(&block)
394
+ self.redefine_callback(:before_recovery, &block)
395
+ end # before_recovery(&block)
396
+
397
+ # @private
398
+ def run_before_recovery_callbacks
399
+ self.exec_callback_yielding_self(:before_recovery)
400
+
401
+ @consumers.each { |tag, c| c.run_before_recovery_callbacks }
402
+ end
403
+
404
+
405
+ # Defines a callback that will be executed when AMQP connection is recovered after a network failure..
406
+ # Only one callback can be defined (the one defined last replaces previously added ones).
407
+ #
408
+ # @api public
409
+ def on_recovery(&block)
410
+ self.redefine_callback(:after_recovery, &block)
411
+ end # on_recovery(&block)
412
+ alias after_recovery on_recovery
413
+
414
+ # @private
415
+ def run_after_recovery_callbacks
416
+ self.exec_callback_yielding_self(:after_recovery)
417
+
418
+ @consumers.each { |tag, c| c.run_after_recovery_callbacks }
419
+ end
420
+
421
+
422
+
423
+ # Called by associated connection object when AMQP connection has been re-established
424
+ # (for example, after a network failure).
425
+ #
426
+ # @api plugin
427
+ def auto_recover
428
+ self.exec_callback_yielding_self(:before_recovery)
429
+ self.redeclare do
430
+ self.rebind
431
+
432
+ @consumers.each { |tag, consumer| consumer.auto_recover }
307
433
 
434
+ self.exec_callback_yielding_self(:after_recovery)
435
+ end
436
+ end # auto_recover
437
+
438
+ # @endgroup
308
439
 
309
440
 
310
441
  #
311
442
  # Implementation
312
443
  #
313
444
 
445
+
446
+ # Unique string supposed to be used as a consumer tag.
447
+ #
448
+ # @return [String] Unique string.
449
+ # @api plugin
450
+ def generate_consumer_tag(name)
451
+ "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
452
+ end
453
+
454
+
455
+ def handle_connection_interruption(method = nil)
456
+ @consumers.each { |tag, c| c.handle_connection_interruption(method) }
457
+ end # handle_connection_interruption(method = nil)
458
+
459
+
314
460
  def handle_declare_ok(method)
315
- @name = method.queue if self.anonymous?
461
+ @name = method.queue if @name.empty?
316
462
  @channel.register_queue(self)
317
463
 
318
464
  self.exec_callback_once_yielding_self(:declare, method)
@@ -322,10 +468,6 @@ module AMQ
322
468
  self.exec_callback_once(:delete, method)
323
469
  end # handle_delete_ok(method)
324
470
 
325
- def handle_consume_ok(method)
326
- self.exec_callback_once(:consume, method)
327
- end # handle_consume_ok(method)
328
-
329
471
  def handle_purge_ok(method)
330
472
  self.exec_callback_once(:purge, method)
331
473
  end # handle_purge_ok(method)
@@ -338,15 +480,6 @@ module AMQ
338
480
  self.exec_callback_once(:unbind, method)
339
481
  end # handle_unbind_ok(method)
340
482
 
341
- def handle_delivery(method, header, payload)
342
- self.exec_callback(:delivery, method, header, payload)
343
- end # handle_delivery
344
-
345
- def handle_cancel_ok(method)
346
- @consumer_tag = nil
347
- self.exec_callback_once(:cancel, method)
348
- end # handle_cancel_ok(method)
349
-
350
483
  def handle_get_ok(method, header, payload)
351
484
  method = Protocol::GetResponse.new(method)
352
485
  self.exec_callback(:get, method, header, payload)
@@ -394,35 +527,6 @@ module AMQ
394
527
  end
395
528
 
396
529
 
397
- self.handle(Protocol::Basic::ConsumeOk) do |connection, frame|
398
- channel = connection.channels[frame.channel]
399
- queue = channel.queues_awaiting_consume_ok.shift
400
-
401
- queue.handle_consume_ok(frame.decode_payload)
402
- end
403
-
404
-
405
- self.handle(Protocol::Basic::CancelOk) do |connection, frame|
406
- channel = connection.channels[frame.channel]
407
- queue = channel.queues_awaiting_cancel_ok.shift
408
-
409
- queue.handle_consume_ok(frame.decode_payload)
410
- end
411
-
412
-
413
- # Basic.Deliver
414
- self.handle(Protocol::Basic::Deliver) do |connection, method_frame, content_frames|
415
- channel = connection.channels[method_frame.channel]
416
- method = method_frame.decode_payload
417
- queue = channel.consumers[method.consumer_tag]
418
-
419
- header = content_frames.shift
420
- body = content_frames.map { |frame| frame.payload }.join
421
- queue.handle_delivery(method, header, body)
422
- # TODO: ack if necessary
423
- end
424
-
425
-
426
530
  self.handle(Protocol::Queue::PurgeOk) do |connection, frame|
427
531
  channel = connection.channels[frame.channel]
428
532
  queue = channel.queues_awaiting_purge_ok.shift
@@ -449,7 +553,7 @@ module AMQ
449
553
 
450
554
  queue.handle_get_empty(frame.decode_payload)
451
555
  end
452
- end # Queue
556
+ end # Queue
453
557
  end # Async
454
558
  end # Client
455
559
  end # AMQ