message-driver 0.1.0 → 0.2.0.rc1
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +18 -7
- data/CHANGELOG.md +12 -2
- data/Gemfile +17 -0
- data/Guardfile +8 -4
- data/README.md +14 -5
- data/Rakefile +44 -11
- data/examples/basic_producer_and_consumer/Gemfile +5 -0
- data/examples/basic_producer_and_consumer/common.rb +17 -0
- data/examples/basic_producer_and_consumer/consumer.rb +24 -0
- data/examples/basic_producer_and_consumer/producer.rb +33 -0
- data/features/.nav +8 -0
- data/features/CHANGELOG.md +12 -2
- data/features/amqp_specific_features/binding_amqp_destinations.feature +7 -7
- data/features/amqp_specific_features/declaring_amqp_exchanges.feature +3 -3
- data/features/amqp_specific_features/nack_redelivered_messages.feature +92 -0
- data/features/amqp_specific_features/requeueing_on_nack.feature +44 -0
- data/features/amqp_specific_features/server_named_destinations.feature +5 -5
- data/features/client_acks.feature +92 -0
- data/features/destination_metadata.feature +9 -11
- data/features/dynamic_destinations.feature +7 -7
- data/features/error_handling.feature +11 -9
- data/features/logging.feature +14 -0
- data/features/message_consumers/auto_ack_consumers.feature +79 -0
- data/features/message_consumers/manual_ack_consumers.feature +95 -0
- data/features/message_consumers/transactional_ack_consumers.feature +77 -0
- data/features/message_consumers.feature +54 -0
- data/features/publishing_a_message.feature +6 -10
- data/features/publishing_with_transactions.feature +10 -14
- data/features/rabbitmq_specific_features/dead_letter_queueing.feature +116 -0
- data/features/step_definitions/dynamic_destinations_steps.rb +3 -3
- data/features/step_definitions/error_handling_steps.rb +4 -2
- data/features/step_definitions/logging_steps.rb +28 -0
- data/features/step_definitions/message_consumers_steps.rb +29 -0
- data/features/step_definitions/steps.rb +60 -9
- data/features/support/broker_config_helper.rb +19 -0
- data/features/support/env.rb +1 -0
- data/features/support/firewall_helper.rb +8 -11
- data/features/support/message_table_matcher.rb +21 -5
- data/features/support/test_runner.rb +39 -16
- data/lib/message_driver/adapters/base.rb +51 -4
- data/lib/message_driver/adapters/bunny_adapter.rb +251 -127
- data/lib/message_driver/adapters/in_memory_adapter.rb +97 -18
- data/lib/message_driver/adapters/stomp_adapter.rb +127 -0
- data/lib/message_driver/broker.rb +23 -24
- data/lib/message_driver/client.rb +157 -0
- data/lib/message_driver/destination.rb +7 -4
- data/lib/message_driver/errors.rb +27 -0
- data/lib/message_driver/logging.rb +11 -0
- data/lib/message_driver/message.rb +8 -0
- data/lib/message_driver/subscription.rb +18 -0
- data/lib/message_driver/vendor/.document +0 -0
- data/lib/message_driver/vendor/nesty/nested_error.rb +26 -0
- data/lib/message_driver/vendor/nesty.rb +1 -0
- data/lib/message_driver/version.rb +1 -1
- data/lib/message_driver.rb +4 -2
- data/message-driver.gemspec +4 -4
- data/spec/integration/{amqp_integration_spec.rb → bunny/amqp_integration_spec.rb} +29 -28
- data/spec/integration/bunny/bunny_adapter_spec.rb +339 -0
- data/spec/integration/in_memory/in_memory_adapter_spec.rb +126 -0
- data/spec/integration/stomp/stomp_adapter_spec.rb +142 -0
- data/spec/spec_helper.rb +5 -2
- data/spec/support/shared/adapter_examples.rb +17 -0
- data/spec/support/shared/client_ack_examples.rb +18 -0
- data/spec/support/shared/context_examples.rb +14 -0
- data/spec/support/shared/destination_examples.rb +4 -5
- data/spec/support/shared/subscription_examples.rb +146 -0
- data/spec/support/shared/transaction_examples.rb +43 -0
- data/spec/support/utils.rb +14 -0
- data/spec/units/message_driver/adapters/base_spec.rb +38 -19
- data/spec/units/message_driver/broker_spec.rb +71 -18
- data/spec/units/message_driver/client_spec.rb +375 -0
- data/spec/units/message_driver/destination_spec.rb +9 -0
- data/spec/units/message_driver/logging_spec.rb +18 -0
- data/spec/units/message_driver/message_spec.rb +36 -0
- data/spec/units/message_driver/subscription_spec.rb +24 -0
- data/test_lib/broker_config.rb +50 -20
- metadata +83 -45
- data/.rbenv-version +0 -1
- data/lib/message_driver/exceptions.rb +0 -18
- data/lib/message_driver/message_publisher.rb +0 -15
- data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +0 -301
- data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +0 -43
- data/spec/units/message_driver/message_publisher_spec.rb +0 -65
@@ -9,6 +9,7 @@ module MessageDriver
|
|
9
9
|
|
10
10
|
module Adapters
|
11
11
|
class BunnyAdapter < Base
|
12
|
+
NETWORK_ERRORS = [Bunny::TCPConnectionFailed, Bunny::ConnectionClosedError, Bunny::ConnectionLevelException, Bunny::NetworkErrorWrapper, Bunny::NetworkFailure, IOError].freeze
|
12
13
|
|
13
14
|
class Message < MessageDriver::Message::Base
|
14
15
|
attr_reader :delivery_info
|
@@ -17,13 +18,21 @@ module MessageDriver
|
|
17
18
|
super(payload, properties[:headers]||{}, properties)
|
18
19
|
@delivery_info = delivery_info
|
19
20
|
end
|
21
|
+
|
22
|
+
def delivery_tag
|
23
|
+
delivery_info.delivery_tag
|
24
|
+
end
|
25
|
+
|
26
|
+
def redelivered?
|
27
|
+
delivery_info.redelivered?
|
28
|
+
end
|
20
29
|
end
|
21
30
|
|
22
31
|
class Destination < MessageDriver::Destination::Base
|
23
|
-
def
|
32
|
+
def publish_params(headers, properties)
|
24
33
|
props = @message_props.merge(properties)
|
25
34
|
props[:headers] = headers if headers
|
26
|
-
|
35
|
+
[exchange_name, routing_key(properties), props]
|
27
36
|
end
|
28
37
|
|
29
38
|
def exchange_name
|
@@ -36,24 +45,31 @@ module MessageDriver
|
|
36
45
|
end
|
37
46
|
|
38
47
|
class QueueDestination < Destination
|
39
|
-
def after_initialize
|
48
|
+
def after_initialize(adapter_context)
|
40
49
|
unless @dest_options[:no_declare]
|
41
|
-
|
42
|
-
|
43
|
-
@name = queue.name
|
44
|
-
if bindings = @dest_options[:bindings]
|
45
|
-
bindings.each do |bnd|
|
46
|
-
raise MessageDriver::Exception, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
|
47
|
-
queue.bind(bnd[:source], bnd[:args]||{})
|
48
|
-
end
|
49
|
-
end
|
50
|
+
adapter_context.with_channel(false) do |ch|
|
51
|
+
bunny_queue(ch, true)
|
50
52
|
end
|
51
53
|
else
|
52
|
-
raise MessageDriver::
|
53
|
-
raise MessageDriver::
|
54
|
+
raise MessageDriver::Error, "server-named queues must be declared, but you provided :no_declare => true" if @name.empty?
|
55
|
+
raise MessageDriver::Error, "queues with bindings must be declared, but you provided :no_declare => true" if @dest_options[:bindings]
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
59
|
+
def bunny_queue(channel, initialize=false)
|
60
|
+
queue = channel.queue(@name, @dest_options)
|
61
|
+
if initialize
|
62
|
+
@name = queue.name
|
63
|
+
if bindings = @dest_options[:bindings]
|
64
|
+
bindings.each do |bnd|
|
65
|
+
raise MessageDriver::Error, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
|
66
|
+
queue.bind(bnd[:source], bnd[:args]||{})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
queue
|
71
|
+
end
|
72
|
+
|
57
73
|
def exchange_name
|
58
74
|
""
|
59
75
|
end
|
@@ -63,29 +79,31 @@ module MessageDriver
|
|
63
79
|
end
|
64
80
|
|
65
81
|
def message_count
|
66
|
-
|
82
|
+
Client.current_adapter_context.with_channel(false) do |ch|
|
67
83
|
ch.queue(@name, @dest_options.merge(passive: true)).message_count
|
68
84
|
end
|
69
85
|
end
|
70
|
-
end
|
71
86
|
|
72
|
-
|
73
|
-
|
74
|
-
|
87
|
+
def purge
|
88
|
+
Client.current_adapter_context.with_channel(false) do |ch|
|
89
|
+
bunny_queue(ch).purge
|
90
|
+
end
|
75
91
|
end
|
92
|
+
end
|
76
93
|
|
77
|
-
|
94
|
+
class ExchangeDestination < Destination
|
95
|
+
def after_initialize(adapter_context)
|
78
96
|
if declare = @dest_options[:declare]
|
79
|
-
|
97
|
+
adapter_context.with_channel(false) do |ch|
|
80
98
|
type = declare.delete(:type)
|
81
|
-
raise MessageDriver::
|
99
|
+
raise MessageDriver::Error, "you must provide a valid exchange type" unless type
|
82
100
|
ch.exchange_declare(@name, type, declare)
|
83
101
|
end
|
84
102
|
end
|
85
103
|
if bindings = @dest_options[:bindings]
|
86
|
-
|
104
|
+
adapter_context.with_channel(false) do |ch|
|
87
105
|
bindings.each do |bnd|
|
88
|
-
raise MessageDriver::
|
106
|
+
raise MessageDriver::Error, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
|
89
107
|
ch.exchange_bind(bnd[:source], @name, bnd[:args]||{})
|
90
108
|
end
|
91
109
|
end
|
@@ -93,139 +111,261 @@ module MessageDriver
|
|
93
111
|
end
|
94
112
|
end
|
95
113
|
|
114
|
+
class Subscription < Subscription::Base
|
115
|
+
def start
|
116
|
+
raise MessageDriver::Error, "subscriptions are only supported with QueueDestinations" unless destination.is_a? QueueDestination
|
117
|
+
@sub_ctx = adapter.new_subscription_context(self)
|
118
|
+
@error_handler = options[:error_handler]
|
119
|
+
@sub_ctx.with_channel do |ch|
|
120
|
+
queue = destination.bunny_queue(@sub_ctx.channel)
|
121
|
+
ack_mode = case options[:ack]
|
122
|
+
when :auto, nil
|
123
|
+
:auto
|
124
|
+
when :manual
|
125
|
+
:manual
|
126
|
+
when :transactional
|
127
|
+
:transactional
|
128
|
+
else
|
129
|
+
raise MessageDriver::Error, "unrecognized :ack option #{options[:ack]}"
|
130
|
+
end
|
131
|
+
@bunny_consumer = queue.subscribe(options.merge(manual_ack: true)) do |delivery_info, properties, payload|
|
132
|
+
message = @sub_ctx.args_to_message(delivery_info, properties, payload)
|
133
|
+
Client.with_adapter_context(@sub_ctx) do
|
134
|
+
begin
|
135
|
+
case ack_mode
|
136
|
+
when :auto
|
137
|
+
consumer.call(message)
|
138
|
+
@sub_ctx.ack_message(message)
|
139
|
+
when :manual
|
140
|
+
consumer.call(message)
|
141
|
+
when :transactional
|
142
|
+
Client.with_message_transaction do
|
143
|
+
consumer.call(message)
|
144
|
+
@sub_ctx.ack_message(message)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
rescue => e
|
148
|
+
if e.is_a?(DontRequeue) || (options[:retry_redelivered] == false && message.redelivered?)
|
149
|
+
if [:auto, :transactional].include? ack_mode
|
150
|
+
@sub_ctx.nack_message(message, requeue: false)
|
151
|
+
end
|
152
|
+
else
|
153
|
+
if @sub_ctx.valid? && ack_mode == :auto
|
154
|
+
begin
|
155
|
+
@sub_ctx.nack_message(message, requeue: true)
|
156
|
+
rescue => e
|
157
|
+
logger.error exception_to_str(e)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
@error_handler.call(e, message) unless @error_handler.nil?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def unsubscribe
|
169
|
+
unless @bunny_consumer.nil?
|
170
|
+
@bunny_consumer.cancel
|
171
|
+
@bunny_consumer = nil
|
172
|
+
end
|
173
|
+
unless @sub_ctx.nil?
|
174
|
+
@sub_ctx.invalidate(true)
|
175
|
+
@sub_ctx = nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
96
180
|
def initialize(config)
|
97
181
|
validate_bunny_version
|
98
|
-
|
99
|
-
@connection = Bunny.new(config.merge(threaded: false))
|
182
|
+
@config = config
|
100
183
|
end
|
101
184
|
|
102
185
|
def connection(ensure_started=true)
|
186
|
+
@connection ||= Bunny.new(@config)
|
103
187
|
if ensure_started && !@connection.open?
|
104
|
-
|
188
|
+
begin
|
189
|
+
@connection.start
|
190
|
+
rescue *NETWORK_ERRORS => e
|
191
|
+
raise MessageDriver::ConnectionError.new(e.to_s, e)
|
192
|
+
end
|
105
193
|
end
|
106
194
|
@connection
|
107
195
|
end
|
108
196
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
197
|
+
def stop
|
198
|
+
begin
|
199
|
+
super
|
200
|
+
@connection.close if !@connection.nil? && @connection.open?
|
201
|
+
rescue *NETWORK_ERRORS => e
|
202
|
+
logger.error "error while attempting connection close\n#{exception_to_str(e)}"
|
112
203
|
end
|
113
204
|
end
|
114
205
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
206
|
+
def build_context
|
207
|
+
BunnyContext.new(self)
|
208
|
+
end
|
209
|
+
|
210
|
+
def new_subscription_context(subscription)
|
211
|
+
ctx = new_context
|
212
|
+
ctx.channel = connection.create_channel
|
213
|
+
ctx.subscription = subscription
|
214
|
+
ctx
|
215
|
+
end
|
216
|
+
|
217
|
+
class BunnyContext < ContextBase
|
218
|
+
attr_accessor :channel, :subscription
|
118
219
|
|
119
|
-
|
120
|
-
|
121
|
-
|
220
|
+
def initialize(adapter)
|
221
|
+
super(adapter)
|
222
|
+
@is_transactional = false
|
223
|
+
@rollback_only = false
|
224
|
+
@need_channel_reset = false
|
225
|
+
@in_transaction = false
|
226
|
+
end
|
227
|
+
|
228
|
+
def create_destination(name, dest_options={}, message_props={})
|
229
|
+
dest = case type = dest_options.delete(:type)
|
230
|
+
when :exchange
|
231
|
+
ExchangeDestination.new(self.adapter, name, dest_options, message_props)
|
232
|
+
when :queue, nil
|
233
|
+
QueueDestination.new(self.adapter, name, dest_options, message_props)
|
122
234
|
else
|
123
|
-
|
235
|
+
raise MessageDriver::Error, "invalid destination type #{type}"
|
124
236
|
end
|
237
|
+
dest.after_initialize(self)
|
238
|
+
dest
|
125
239
|
end
|
126
|
-
end
|
127
240
|
|
128
|
-
|
129
|
-
|
130
|
-
when :exchange
|
131
|
-
ExchangeDestination.new(self, name, dest_options, message_props)
|
132
|
-
when :queue, nil
|
133
|
-
QueueDestination.new(self, name, dest_options, message_props)
|
134
|
-
else
|
135
|
-
raise MessageDriver::Exception, "invalid destination type #{type}"
|
241
|
+
def supports_transactions?
|
242
|
+
true
|
136
243
|
end
|
137
|
-
end
|
138
244
|
|
139
|
-
|
140
|
-
|
141
|
-
|
245
|
+
def begin_transaction(options={})
|
246
|
+
raise MessageDriver::TransactionError, "you can't begin another transaction, you are already in one!" if in_transaction?
|
247
|
+
unless is_transactional?
|
248
|
+
with_channel(false) do |ch|
|
249
|
+
ch.tx_select
|
250
|
+
end
|
251
|
+
@is_transactional = true
|
252
|
+
end
|
253
|
+
@in_transaction = true
|
254
|
+
end
|
142
255
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
256
|
+
def commit_transaction(channel_commit=false)
|
257
|
+
raise MessageDriver::TransactionError, "you can't finish the transaction unless you already in one!" if !in_transaction? && !channel_commit
|
258
|
+
begin
|
259
|
+
if is_transactional? && valid? && !@need_channel_reset
|
260
|
+
if @rollback_only
|
261
|
+
@channel.tx_rollback
|
262
|
+
else
|
263
|
+
@channel.tx_commit
|
264
|
+
end
|
265
|
+
end
|
266
|
+
ensure
|
267
|
+
@rollback_only = false
|
268
|
+
@in_transaction = false
|
269
|
+
end
|
270
|
+
end
|
147
271
|
|
148
|
-
|
149
|
-
|
150
|
-
|
272
|
+
def rollback_transaction
|
273
|
+
@rollback_only = true
|
274
|
+
commit_transaction
|
151
275
|
end
|
152
|
-
@context ||= ChannelContext.new(connection)
|
153
|
-
end
|
154
276
|
|
155
|
-
|
277
|
+
def is_transactional?
|
278
|
+
@is_transactional
|
279
|
+
end
|
156
280
|
|
157
|
-
|
158
|
-
|
281
|
+
def in_transaction?
|
282
|
+
@in_transaction
|
283
|
+
end
|
159
284
|
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@rollback_only = false
|
166
|
-
@need_channel_reset = false
|
167
|
-
@connection_failed = false
|
285
|
+
def publish(destination, body, headers={}, properties={})
|
286
|
+
exchange, routing_key, props = *destination.publish_params(headers, properties)
|
287
|
+
with_channel(true) do |ch|
|
288
|
+
ch.basic_publish(body, exchange, routing_key, props)
|
289
|
+
end
|
168
290
|
end
|
169
291
|
|
170
|
-
def
|
171
|
-
|
292
|
+
def pop_message(destination, options={})
|
293
|
+
raise MessageDriver::Error, "You can't pop a message off an exchange" if destination.is_a? ExchangeDestination
|
294
|
+
|
295
|
+
with_channel(false) do |ch|
|
296
|
+
queue = ch.queue(destination.name, passive: true)
|
297
|
+
|
298
|
+
message = queue.pop(ack: !!options[:client_ack])
|
299
|
+
if message.nil? || message[0].nil?
|
300
|
+
nil
|
301
|
+
else
|
302
|
+
args_to_message(*message)
|
303
|
+
end
|
304
|
+
end
|
172
305
|
end
|
173
306
|
|
174
|
-
def
|
175
|
-
|
307
|
+
def supports_client_acks?
|
308
|
+
true
|
176
309
|
end
|
177
310
|
|
178
|
-
def
|
179
|
-
|
180
|
-
|
181
|
-
@is_transactional = true
|
311
|
+
def ack_message(message, options={})
|
312
|
+
with_channel(true) do |ch|
|
313
|
+
ch.ack(message.delivery_tag)
|
182
314
|
end
|
315
|
+
end
|
183
316
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
317
|
+
def nack_message(message, options={})
|
318
|
+
requeue = options[:requeue].kind_of?(FalseClass) ? false : true
|
319
|
+
with_channel(true) do |ch|
|
320
|
+
ch.reject(message.delivery_tag, requeue)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def supports_subscriptions?
|
325
|
+
true
|
326
|
+
end
|
327
|
+
|
328
|
+
def subscribe(destination, options={}, &consumer)
|
329
|
+
sub = Subscription.new(adapter, destination, consumer, options)
|
330
|
+
sub.start
|
331
|
+
sub
|
332
|
+
end
|
333
|
+
|
334
|
+
def invalidate(in_unsubscribe=false)
|
335
|
+
super()
|
336
|
+
unless @subscription.nil? || in_unsubscribe
|
337
|
+
@subscription.unsubscribe
|
338
|
+
end
|
339
|
+
unless @channel.nil?
|
340
|
+
@channel.close if @channel.open?
|
193
341
|
end
|
194
342
|
end
|
195
343
|
|
196
344
|
def with_channel(require_commit=true)
|
197
345
|
raise MessageDriver::TransactionRollbackOnly if @rollback_only
|
198
|
-
raise MessageDriver::
|
346
|
+
raise MessageDriver::Error, "oh nos!" if !valid?
|
347
|
+
@channel = adapter.connection.create_channel if @channel.nil?
|
199
348
|
reset_channel if @need_channel_reset
|
200
349
|
begin
|
201
350
|
result = yield @channel
|
202
|
-
commit_transaction(true) if require_commit
|
351
|
+
commit_transaction(true) if require_commit && is_transactional? && !in_transaction?
|
203
352
|
result
|
204
353
|
rescue Bunny::ChannelLevelException => e
|
205
354
|
@need_channel_reset = true
|
206
|
-
@rollback_only = true if
|
355
|
+
@rollback_only = true if in_transaction?
|
207
356
|
if e.kind_of? Bunny::NotFound
|
208
|
-
raise MessageDriver::QueueNotFound.new(e)
|
357
|
+
raise MessageDriver::QueueNotFound.new(e.to_s, e)
|
209
358
|
else
|
210
|
-
raise MessageDriver::
|
359
|
+
raise MessageDriver::WrappedError.new(e.to_s, e)
|
211
360
|
end
|
212
|
-
rescue
|
213
|
-
@
|
214
|
-
|
215
|
-
raise MessageDriver::ConnectionException.new(e)
|
361
|
+
rescue *NETWORK_ERRORS => e
|
362
|
+
@rollback_only = true if in_transaction?
|
363
|
+
raise MessageDriver::ConnectionError.new(e.to_s, e)
|
216
364
|
end
|
217
365
|
end
|
218
366
|
|
219
|
-
def
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
|
-
def need_new_context?
|
224
|
-
if is_transactional?
|
225
|
-
!within_transaction? && connection_failed?
|
226
|
-
else
|
227
|
-
connection_failed?
|
228
|
-
end
|
367
|
+
def args_to_message(delivery_info, properties, payload)
|
368
|
+
Message.new(delivery_info, properties, payload)
|
229
369
|
end
|
230
370
|
|
231
371
|
private
|
@@ -234,35 +374,19 @@ module MessageDriver
|
|
234
374
|
unless @channel.open?
|
235
375
|
@channel.open
|
236
376
|
@is_transactional = false
|
377
|
+
@rollback_only = true if in_transaction?
|
237
378
|
end
|
238
379
|
@need_channel_reset = false
|
239
380
|
end
|
240
|
-
|
241
|
-
def commit_transaction(from_channel=false)
|
242
|
-
threshold = from_channel ? 0 : 1
|
243
|
-
if is_transactional? && @transaction_depth <= threshold && !connection_failed?
|
244
|
-
unless @need_channel_reset
|
245
|
-
unless @rollback_only
|
246
|
-
@channel.tx_commit
|
247
|
-
else
|
248
|
-
@channel.tx_rollback
|
249
|
-
end
|
250
|
-
end
|
251
|
-
@rollback_only = false
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
def rollback_transaction
|
256
|
-
@rollback_only = true
|
257
|
-
commit_transaction
|
258
|
-
end
|
259
381
|
end
|
260
382
|
|
383
|
+
private
|
384
|
+
|
261
385
|
def validate_bunny_version
|
262
|
-
required = Gem::Requirement.create('
|
386
|
+
required = Gem::Requirement.create('>= 0.9.3')
|
263
387
|
current = Gem::Version.create(Bunny::VERSION)
|
264
388
|
unless required.satisfied_by? current
|
265
|
-
raise MessageDriver::
|
389
|
+
raise MessageDriver::Error, "bunny 0.9.3 or later is required for the bunny adapter"
|
266
390
|
end
|
267
391
|
end
|
268
392
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module MessageDriver
|
2
4
|
class Broker
|
3
5
|
def in_memory_adapter
|
@@ -9,49 +11,126 @@ module MessageDriver
|
|
9
11
|
class InMemoryAdapter < Base
|
10
12
|
|
11
13
|
class Message < MessageDriver::Message::Base
|
14
|
+
end
|
12
15
|
|
16
|
+
class Subscription < MessageDriver::Subscription::Base
|
17
|
+
def unsubscribe
|
18
|
+
adapter.remove_subscription_for(destination.name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def deliver_message(message)
|
22
|
+
begin
|
23
|
+
consumer.call(message)
|
24
|
+
rescue => e
|
25
|
+
unless options[:error_handler].nil?
|
26
|
+
options[:error_handler].call(e, message)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
13
30
|
end
|
14
31
|
|
15
32
|
class Destination < MessageDriver::Destination::Base
|
16
|
-
|
17
|
-
|
18
|
-
|
33
|
+
|
34
|
+
def subscription
|
35
|
+
adapter.subscription_for(name)
|
19
36
|
end
|
37
|
+
|
20
38
|
def message_count
|
21
|
-
|
39
|
+
message_queue.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def pop_message(options={})
|
43
|
+
message_queue.shift
|
44
|
+
end
|
45
|
+
|
46
|
+
def subscribe(options={}, &consumer)
|
47
|
+
subscription = Subscription.new(adapter, self, consumer, options)
|
48
|
+
adapter.set_subscription_for(name, subscription)
|
49
|
+
until (msg = pop_message).nil?
|
50
|
+
subscription.deliver_message(msg)
|
51
|
+
end
|
52
|
+
subscription
|
53
|
+
end
|
54
|
+
|
55
|
+
def publish(body, headers={}, properties={})
|
56
|
+
msg = Message.new(body, headers, properties)
|
57
|
+
sub = subscription
|
58
|
+
if sub.nil?
|
59
|
+
message_queue << msg
|
60
|
+
else
|
61
|
+
sub.deliver_message(msg)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def message_queue
|
67
|
+
adapter.message_queue_for(name)
|
22
68
|
end
|
23
69
|
end
|
24
70
|
|
25
71
|
def initialize(config={})
|
26
|
-
|
72
|
+
@destinations = {}
|
73
|
+
@message_store = Hash.new { |h,k| h[k] = [] }
|
74
|
+
@subscriptions = Hash.new
|
27
75
|
end
|
28
76
|
|
29
|
-
def
|
30
|
-
|
77
|
+
def build_context
|
78
|
+
InMemoryContext.new(self)
|
31
79
|
end
|
32
80
|
|
33
|
-
def
|
34
|
-
|
81
|
+
def create_destination(name, dest_options={}, message_props={})
|
82
|
+
destination = Destination.new(self, name, dest_options, message_props)
|
83
|
+
@destinations[name] = destination
|
35
84
|
end
|
36
85
|
|
37
|
-
|
38
|
-
|
86
|
+
class InMemoryContext < ContextBase
|
87
|
+
extend Forwardable
|
88
|
+
|
89
|
+
def_delegators :adapter, :create_destination
|
90
|
+
|
91
|
+
def publish(destination, body, headers={}, properties={})
|
92
|
+
destination.publish(body, headers, properties)
|
93
|
+
end
|
94
|
+
|
95
|
+
def pop_message(destination, options={})
|
96
|
+
destination.pop_message(options)
|
97
|
+
end
|
98
|
+
|
99
|
+
def subscribe(destination, options={}, &consumer)
|
100
|
+
destination.subscribe(options, &consumer)
|
101
|
+
end
|
102
|
+
|
103
|
+
def supports_subscriptions?
|
104
|
+
true
|
105
|
+
end
|
39
106
|
end
|
40
107
|
|
41
|
-
def
|
42
|
-
|
108
|
+
def stop
|
109
|
+
super
|
110
|
+
reset_after_tests
|
43
111
|
end
|
44
112
|
|
45
113
|
def reset_after_tests
|
46
|
-
message_store.each do |
|
47
|
-
|
114
|
+
@message_store.keys.each do |k|
|
115
|
+
@message_store[k] = []
|
48
116
|
end
|
117
|
+
@subscriptions.clear
|
118
|
+
end
|
119
|
+
|
120
|
+
def message_queue_for(name)
|
121
|
+
@message_store[name]
|
122
|
+
end
|
123
|
+
|
124
|
+
def subscription_for(name)
|
125
|
+
@subscriptions[name]
|
49
126
|
end
|
50
127
|
|
51
|
-
|
128
|
+
def set_subscription_for(name, subscription)
|
129
|
+
@subscriptions[name] = subscription
|
130
|
+
end
|
52
131
|
|
53
|
-
def
|
54
|
-
@
|
132
|
+
def remove_subscription_for(name)
|
133
|
+
@subscriptions.delete(name)
|
55
134
|
end
|
56
135
|
end
|
57
136
|
end
|