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