amq-client 0.7.0.alpha34 → 0.7.0.alpha35
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Gemfile +1 -1
- data/README.textile +1 -1
- data/bin/ci/before_build.sh +24 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +2 -2
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +1 -1
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +1 -1
- data/lib/amq/client.rb +29 -17
- data/lib/amq/client/adapter.rb +8 -504
- data/lib/amq/client/adapters/coolio.rb +4 -282
- data/lib/amq/client/adapters/event_machine.rb +4 -382
- data/lib/amq/client/async/adapter.rb +517 -0
- data/lib/amq/client/async/adapters/coolio.rb +291 -0
- data/lib/amq/client/async/adapters/event_machine.rb +392 -0
- data/lib/amq/client/async/adapters/eventmachine.rb +1 -0
- data/lib/amq/client/async/callbacks.rb +71 -0
- data/lib/amq/client/async/channel.rb +385 -0
- data/lib/amq/client/async/entity.rb +66 -0
- data/lib/amq/client/async/exchange.rb +157 -0
- data/lib/amq/client/async/extensions/rabbitmq/basic.rb +38 -0
- data/lib/amq/client/async/extensions/rabbitmq/confirm.rb +248 -0
- data/lib/amq/client/async/queue.rb +455 -0
- data/lib/amq/client/callbacks.rb +6 -65
- data/lib/amq/client/channel.rb +4 -376
- data/lib/amq/client/entity.rb +6 -57
- data/lib/amq/client/exchange.rb +4 -148
- data/lib/amq/client/extensions/rabbitmq/basic.rb +4 -28
- data/lib/amq/client/extensions/rabbitmq/confirm.rb +5 -240
- data/lib/amq/client/queue.rb +5 -450
- data/lib/amq/client/version.rb +1 -1
- data/spec/unit/client_spec.rb +10 -30
- 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
|