amq-client 0.7.0.alpha34 → 0.7.0.alpha35

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 (32) hide show
  1. data/.travis.yml +4 -0
  2. data/Gemfile +1 -1
  3. data/README.textile +1 -1
  4. data/bin/ci/before_build.sh +24 -0
  5. data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +2 -2
  6. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +1 -1
  7. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +1 -1
  8. data/lib/amq/client.rb +29 -17
  9. data/lib/amq/client/adapter.rb +8 -504
  10. data/lib/amq/client/adapters/coolio.rb +4 -282
  11. data/lib/amq/client/adapters/event_machine.rb +4 -382
  12. data/lib/amq/client/async/adapter.rb +517 -0
  13. data/lib/amq/client/async/adapters/coolio.rb +291 -0
  14. data/lib/amq/client/async/adapters/event_machine.rb +392 -0
  15. data/lib/amq/client/async/adapters/eventmachine.rb +1 -0
  16. data/lib/amq/client/async/callbacks.rb +71 -0
  17. data/lib/amq/client/async/channel.rb +385 -0
  18. data/lib/amq/client/async/entity.rb +66 -0
  19. data/lib/amq/client/async/exchange.rb +157 -0
  20. data/lib/amq/client/async/extensions/rabbitmq/basic.rb +38 -0
  21. data/lib/amq/client/async/extensions/rabbitmq/confirm.rb +248 -0
  22. data/lib/amq/client/async/queue.rb +455 -0
  23. data/lib/amq/client/callbacks.rb +6 -65
  24. data/lib/amq/client/channel.rb +4 -376
  25. data/lib/amq/client/entity.rb +6 -57
  26. data/lib/amq/client/exchange.rb +4 -148
  27. data/lib/amq/client/extensions/rabbitmq/basic.rb +4 -28
  28. data/lib/amq/client/extensions/rabbitmq/confirm.rb +5 -240
  29. data/lib/amq/client/queue.rb +5 -450
  30. data/lib/amq/client/version.rb +1 -1
  31. data/spec/unit/client_spec.rb +10 -30
  32. metadata +16 -22
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/openable"
4
+ require "amq/client/async/callbacks"
5
+
6
+ module AMQ
7
+ module Client
8
+ module Async
9
+ module RegisterEntityMixin
10
+ # @example Registering Channel implementation
11
+ # Adapter.register_entity(:channel, Channel)
12
+ # # ... so then I can do:
13
+ # channel = client.channel(1)
14
+ # # instead of:
15
+ # channel = Channel.new(client, 1)
16
+ def register_entity(name, klass)
17
+ define_method(name) do |*args, &block|
18
+ klass.new(self, *args, &block)
19
+ end # define_method
20
+ end # register_entity
21
+ end # RegisterEntityMixin
22
+
23
+ module ProtocolMethodHandlers
24
+ def handle(klass, &block)
25
+ AMQ::Client::HandlersRegistry.register(klass, &block)
26
+ end
27
+
28
+ def handlers
29
+ AMQ::Client::HandlersRegistry.handlers
30
+ end
31
+ end # ProtocolMethodHandlers
32
+
33
+
34
+ # AMQ entities, as implemented by AMQ::Client, have callbacks and can run them
35
+ # when necessary.
36
+ #
37
+ # @note Exchanges and queues implementation is based on this class.
38
+ #
39
+ # @abstract
40
+ module Entity
41
+
42
+ #
43
+ # Behaviors
44
+ #
45
+
46
+ include Openable
47
+ include Async::Callbacks
48
+
49
+ #
50
+ # API
51
+ #
52
+
53
+ # @return [Array<#call>]
54
+ attr_reader :callbacks
55
+
56
+
57
+ def initialize(connection)
58
+ @connection = connection
59
+ # Be careful with default values for #ruby hashes: h = Hash.new(Array.new); h[:key] ||= 1
60
+ # won't assign anything to :key. MK.
61
+ @callbacks = Hash.new
62
+ end # initialize
63
+ end # Entity
64
+ end # Async
65
+ end # Client
66
+ end # AMQ
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/entity"
4
+ require "amq/client/server_named_entity"
5
+
6
+ module AMQ
7
+ module Client
8
+ module Async
9
+ class Exchange
10
+
11
+
12
+ include Entity
13
+ include ServerNamedEntity
14
+ extend ProtocolMethodHandlers
15
+
16
+ TYPES = [:fanout, :direct, :topic, :headers].freeze
17
+
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
+
24
+
25
+ #
26
+ # API
27
+ #
28
+
29
+ # Channel this exchange belongs to.
30
+ attr_reader :channel
31
+
32
+ # Exchange name. May be server-generated or assigned directly.
33
+ attr_reader :name
34
+
35
+ # @return [Symbol] One of :direct, :fanout, :topic, :headers
36
+ attr_reader :type
37
+
38
+ 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
+ end
42
+
43
+ @connection = connection
44
+ @channel = channel
45
+ @name = name
46
+ @type = type
47
+
48
+ # register pre-declared exchanges
49
+ if @name == AMQ::Protocol::EMPTY_STRING || @name =~ /^amq\.(fanout|topic)/
50
+ @channel.register_exchange(self)
51
+ end
52
+
53
+ super(connection)
54
+ end
55
+
56
+
57
+ def fanout?
58
+ @type == :fanout
59
+ end
60
+
61
+ def direct?
62
+ @type == :direct
63
+ end
64
+
65
+ def topic?
66
+ @type == :topic
67
+ end
68
+
69
+
70
+
71
+ def declare(passive = false, durable = false, auto_delete = false, nowait = false, arguments = nil, &block)
72
+ @connection.send_frame(Protocol::Exchange::Declare.encode(@channel.id, @name, @type.to_s, passive, durable, auto_delete, false, nowait, arguments))
73
+
74
+ unless nowait
75
+ self.define_callback(:declare, &block)
76
+ @channel.exchanges_awaiting_declare_ok.push(self)
77
+ end
78
+
79
+ self
80
+ end
81
+
82
+
83
+ def delete(if_unused = false, nowait = false, &block)
84
+ @connection.send_frame(Protocol::Exchange::Delete.encode(@channel.id, @name, if_unused, nowait))
85
+
86
+ unless nowait
87
+ self.define_callback(:delete, &block)
88
+
89
+ # TODO: delete itself from exchanges cache
90
+ @channel.exchanges_awaiting_delete_ok.push(self)
91
+ end
92
+
93
+ self
94
+ end # delete(if_unused = false, nowait = false)
95
+
96
+
97
+ def publish(payload, routing_key = AMQ::Protocol::EMPTY_STRING, user_headers = {}, mandatory = false, immediate = false, frame_size = nil)
98
+ 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)))
100
+
101
+ self
102
+ end
103
+
104
+
105
+ def on_return(&block)
106
+ self.redefine_callback(:return, &block)
107
+
108
+ self
109
+ end # on_return(&block)
110
+
111
+
112
+
113
+
114
+ def handle_declare_ok(method)
115
+ @name = method.exchange if self.anonymous?
116
+ @channel.register_exchange(self)
117
+
118
+ self.exec_callback_once_yielding_self(:declare, method)
119
+ end
120
+
121
+ def handle_delete_ok(method)
122
+ self.exec_callback_once(:delete, method)
123
+ end # handle_delete_ok(method)
124
+
125
+
126
+
127
+ self.handle(Protocol::Exchange::DeclareOk) do |connection, frame|
128
+ method = frame.decode_payload
129
+ channel = connection.channels[frame.channel]
130
+ exchange = channel.exchanges_awaiting_declare_ok.shift
131
+
132
+ exchange.handle_declare_ok(method)
133
+ end # handle
134
+
135
+
136
+ self.handle(Protocol::Exchange::DeleteOk) do |connection, frame|
137
+ channel = connection.channels[frame.channel]
138
+ exchange = channel.exchanges_awaiting_delete_ok.shift
139
+ exchange.handle_delete_ok(frame.decode_payload)
140
+ end # handle
141
+
142
+
143
+ self.handle(Protocol::Basic::Return) do |connection, frame, content_frames|
144
+ channel = connection.channels[frame.channel]
145
+ method = frame.decode_payload
146
+ exchange = channel.find_exchange(method.exchange)
147
+
148
+ header = content_frames.shift
149
+ body = content_frames.map { |frame| frame.payload }.join
150
+
151
+ exchange.exec_callback(:return, method, header, body)
152
+ end
153
+
154
+ end # Exchange
155
+ end # Async
156
+ end # Client
157
+ end # AMQ
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/async/channel"
4
+
5
+ # Basic.Nack
6
+ module AMQ
7
+ module Client
8
+ module Async
9
+ module Extensions
10
+ module RabbitMQ
11
+ module Basic
12
+ module ChannelMixin
13
+
14
+ # Overrides {AMQ::Client::Channel#reject} behavior to use basic.nack.
15
+ #
16
+ # @api public
17
+ # @see http://www.rabbitmq.com/amqp-0-9-1-quickref.html#basic.nack
18
+ def reject(delivery_tag, requeue = true, multi = false)
19
+ if multi
20
+ @connection.send_frame(Protocol::Basic::Nack.encode(self.id, delivery_tag, multi, requeue))
21
+ else
22
+ super(delivery_tag, requeue)
23
+ end
24
+ end # reject
25
+
26
+ end # ChannelMixin
27
+ end # Basic
28
+ end # RabbitMQ
29
+ end # Extensions
30
+
31
+ class Channel
32
+ # use modules, the native Ruby way of extension of existing classes,
33
+ # instead of reckless monkey-patching. MK.
34
+ include Extensions::RabbitMQ::Basic::ChannelMixin
35
+ end # Channel
36
+ end # Async
37
+ end # Client
38
+ end # AMQ
@@ -0,0 +1,248 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQ
4
+ module Client
5
+ module Async
6
+ module Extensions
7
+ module RabbitMQ
8
+ # h2. Purpose
9
+ # In case that the broker crashes, some messages can get lost.
10
+ # Thanks to this extension, broker sends Basic.Ack when the message
11
+ # is processed by the broker. In case of persistent messages, it must
12
+ # be written to disk or ack'd on all the queues it was delivered to.
13
+ # However it doesn't have to be necessarily 1:1, because the broker
14
+ # can send Basic.Ack with multi flag to acknowledge multiple messages.
15
+ #
16
+ # So it provides clients a lightweight way of keeping track of which
17
+ # messages have been processed by the broker and which would need
18
+ # re-publishing in case of broker shutdown or network failure.
19
+ #
20
+ # Transactions are solving the same problem, but they are very slow:
21
+ # confirmations are more than 100 times faster.
22
+ #
23
+ # h2. Workflow
24
+ # * Client asks broker to confirm messages on given channel (Confirm.Select).
25
+ # * Broker sends back Confirm.Select-Ok, unless we sent Confirm.Select with nowait=true.
26
+ # * After each published message, the client receives Basic.Ack from the broker.
27
+ # * If something bad happens inside the broker, it sends Basic.Nack.
28
+ #
29
+ # h2. Gotchas
30
+ # Note that we don't keep track of messages awaiting confirmation.
31
+ # It'd add a huge overhead and it's impossible to come up with one-suits-all solution.
32
+ # If you want to create such module, you'll probably want to redefine Channel#after_publish,
33
+ # so it will put messages into a queue and then handlers for Basic.Ack and Basic.Nack.
34
+ # This is the reason why we pass every argument from Exchange#publish to Channel#after_publish.
35
+ # You should not forget though, that both of these methods can have multi flag!
36
+ #
37
+ # Transactional channel cannot be put into confirm mode and a confirm
38
+ # mode channel cannot be made transactional.
39
+ #
40
+ # If the connection between the publisher and broker drops with outstanding
41
+ # confirms, it does not necessarily mean that the messages were lost, so
42
+ # republishing may result in duplicate messages.
43
+
44
+ # h2. Learn more
45
+ # @see http://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms
46
+ # @see http://www.rabbitmq.com/amqp-0-9-1-quickref.html#class.confirm
47
+ # @see http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.ack
48
+ module Confirm
49
+ module ChannelMixin
50
+
51
+ # Change publisher index. Publisher index is incremented
52
+ # by 1 after each Basic.Publish starting at 1. This is done
53
+ # on both client and server, hence this acknowledged messages
54
+ # can be matched via its delivery-tag.
55
+ #
56
+ # @api private
57
+ attr_writer :publisher_index
58
+
59
+ # Publisher index is an index of the last message since
60
+ # the confirmations were activated, started with 1. It's
61
+ # incremented by 1 after each Basic.Publish starting at 1.
62
+ # This is done on both client and server, hence this
63
+ # acknowledged messages can be matched via its delivery-tag.
64
+ #
65
+ # @return [Integer] Current publisher index.
66
+ # @api public
67
+ def publisher_index
68
+ @publisher_index ||= 1
69
+ end
70
+
71
+ # Resets publisher index to 0
72
+ #
73
+ # @api plugin
74
+ def reset_publisher_index!
75
+ @publisher_index = 0
76
+ end
77
+
78
+
79
+ # This method is executed after publishing of each message via {Exchage#publish}.
80
+ # Currently it just increments publisher index by 1, so messages
81
+ # can be actually matched.
82
+ #
83
+ # @api plugin
84
+ def after_publish(*args)
85
+ self.publisher_index += 1
86
+ end
87
+
88
+ # Turn on confirmations for this channel and, if given,
89
+ # register callback for Confirm.Select-Ok.
90
+ #
91
+ # @raise [RuntimeError] Occurs when confirmations are already activated.
92
+ # @raise [RuntimeError] Occurs when nowait is true and block is given.
93
+ #
94
+ # @param [Boolean] nowait Whether we expect Confirm.Select-Ok to be returned by the broker or not.
95
+ # @yield [method] Callback which will be executed once we receive Confirm.Select-Ok.
96
+ # @yieldparam [AMQ::Protocol::Confirm::SelectOk] method Protocol method class instance.
97
+ #
98
+ # @return [self] self.
99
+ #
100
+ # @see #confirm
101
+ def confirm_select(nowait = false, &block)
102
+ if nowait && block
103
+ raise "You can't use Confirm.Select with nowait=true and a callback at the same time."
104
+ end
105
+
106
+ @uses_publisher_confirmations = true
107
+ self.redefine_callback(:confirm_select, &block)
108
+ @connection.send_frame(Protocol::Confirm::Select.encode(@id, nowait))
109
+
110
+ self
111
+ end
112
+
113
+ # @return [Boolean]
114
+ def uses_publisher_confirmations?
115
+ @uses_publisher_confirmations
116
+ end # uses_publisher_confirmations?
117
+
118
+
119
+ # Turn on confirmations for this channel and, if given,
120
+ # register callback for basic.ack from the broker.
121
+ #
122
+ # @raise [RuntimeError] Occurs when confirmations are already activated.
123
+ # @raise [RuntimeError] Occurs when nowait is true and block is given.
124
+ # @param [Boolean] nowait Whether we expect Confirm.Select-Ok to be returned by the broker or not.
125
+ #
126
+ # @yield [basick_ack] Callback which will be executed every time we receive Basic.Ack from the broker.
127
+ # @yieldparam [AMQ::Protocol::Basic::Ack] basick_ack Protocol method class instance.
128
+ #
129
+ # @return [self] self.
130
+ def on_ack(nowait = false, &block)
131
+ self.use_publisher_confirmations! unless self.uses_publisher_confirmations?
132
+
133
+ self.define_callback(:ack, &block) if block
134
+
135
+ self
136
+ end
137
+
138
+
139
+ # Register error callback for Basic.Nack. It's called
140
+ # when message(s) is rejected.
141
+ #
142
+ # @return [self] self
143
+ def on_nack(&block)
144
+ self.define_callback(:nack, &block) if block
145
+
146
+ self
147
+ end
148
+
149
+
150
+
151
+
152
+ # Handler for Confirm.Select-Ok. By default, it just
153
+ # executes hook specified via the #confirmations method
154
+ # with a single argument, a protocol method class
155
+ # instance (an instance of AMQ::Protocol::Confirm::SelectOk)
156
+ # and then it deletes the callback, since Confirm.Select
157
+ # is supposed to be sent just once.
158
+ #
159
+ # @api plugin
160
+ def handle_select_ok(method)
161
+ self.exec_callback_once(:confirm_select, method)
162
+ end
163
+
164
+ # Handler for Basic.Ack. By default, it just
165
+ # executes hook specified via the #confirm method
166
+ # with a single argument, a protocol method class
167
+ # instance (an instance of AMQ::Protocol::Basic::Ack).
168
+ #
169
+ # @api plugin
170
+ def handle_basic_ack(method)
171
+ self.exec_callback(:ack, method)
172
+ end
173
+
174
+
175
+ # Handler for Basic.Nack. By default, it just
176
+ # executes hook specified via the #confirm_failed method
177
+ # with a single argument, a protocol method class
178
+ # instance (an instance of AMQ::Protocol::Basic::Nack).
179
+ #
180
+ # @api plugin
181
+ def handle_basic_nack(method)
182
+ self.exec_callback(:nack, method)
183
+ end
184
+
185
+
186
+ def reset_state!
187
+ super
188
+
189
+ @uses_publisher_confirmations = false
190
+ end
191
+
192
+
193
+ def self.included(host)
194
+ host.handle(Protocol::Confirm::SelectOk) do |connection, frame|
195
+ method = frame.decode_payload
196
+ channel = connection.channels[frame.channel]
197
+ channel.handle_select_ok(method)
198
+ end
199
+
200
+ host.handle(Protocol::Basic::Ack) do |connection, frame|
201
+ method = frame.decode_payload
202
+ channel = connection.channels[frame.channel]
203
+ channel.handle_basic_ack(method)
204
+ end
205
+
206
+ host.handle(Protocol::Basic::Nack) do |connection, frame|
207
+ method = frame.decode_payload
208
+ channel = connection.channels[frame.channel]
209
+ channel.handle_basic_nack(method)
210
+ end
211
+ end # self.included(host)
212
+ end # ChannelMixin
213
+
214
+
215
+ module ExchangeMixin
216
+ # Publish message and then run #after_publish on channel belonging
217
+ # to the exchange. This is used for incrementing the publisher index.
218
+ #
219
+ # @api public
220
+ # @see AMQ::Client::Exchange#publish
221
+ # @see AMQ::Client::Extensions::RabbitMQ::Channel#publisher_index
222
+ # @return [self] self
223
+ def publish(*args)
224
+ super(*args)
225
+ @channel.after_publish(*args)
226
+
227
+ self
228
+ end # publish
229
+ end # ExchangeMixin
230
+ end # Confirm
231
+ end # RabbitMQ
232
+ end # Extensions
233
+
234
+
235
+ class Channel
236
+ # use modules, a native Ruby way of extension of existing classes,
237
+ # instead of reckless monkey-patching. MK.
238
+ include Extensions::RabbitMQ::Confirm::ChannelMixin
239
+ end # Channel
240
+
241
+ class Exchange
242
+ # use modules, a native Ruby way of extension of existing classes,
243
+ # instead of reckless monkey-patching. MK.
244
+ include Extensions::RabbitMQ::Confirm::ExchangeMixin
245
+ end # Exchange
246
+ end # Async
247
+ end # Client
248
+ end # AMQ