message-driver 0.1.0 → 0.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|