euston-rabbitmq 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/Rakefile +0 -21
- data/euston-rabbitmq.gemspec +26 -37
- data/lib/euston-rabbitmq/{bindings → euston}/command_handler_binder.rb +2 -2
- data/lib/euston-rabbitmq/{errors.rb → euston/errors.rb} +1 -1
- data/lib/euston-rabbitmq/{bindings → euston}/event_handler_binder.rb +4 -4
- data/lib/euston-rabbitmq/{exchanges.rb → euston/exchanges.rb} +1 -1
- data/lib/euston-rabbitmq/{bindings → euston}/handler_binder.rb +2 -1
- data/lib/euston-rabbitmq/{queues.rb → euston/queues.rb} +1 -1
- data/lib/euston-rabbitmq/{subscriptions/retriable_subscription.rb → euston/retrying_subscription.rb} +17 -16
- data/lib/euston-rabbitmq/rabbitmq_client/queue.rb +70 -0
- data/lib/euston-rabbitmq/rabbitmq_client/reactive_message.rb +15 -0
- data/lib/euston-rabbitmq/{constant_loader.rb → reflection/constant_loader.rb} +0 -0
- data/lib/euston-rabbitmq/{handler_finder.rb → reflection/handler_finder.rb} +0 -0
- data/lib/euston-rabbitmq/{handler_reference.rb → reflection/handler_reference.rb} +0 -0
- data/lib/euston-rabbitmq/version.rb +1 -1
- data/lib/euston-rabbitmq.rb +8 -22
- data/spec/euston/command_handler_binder_spec.rb +24 -0
- data/spec/euston/event_handler_binder_spec.rb +36 -0
- data/spec/euston/exchanges_spec.rb +27 -0
- data/spec/euston/queues_spec.rb +24 -0
- data/spec/euston/retrying_subscription_spec.rb +74 -0
- data/spec/rabbitmq_client/queue_spec.rb +69 -0
- data/spec/{constant_loader_spec.rb → reflection/constant_loader_spec.rb} +3 -1
- data/spec/{handler_finder_spec.rb → reflection/handler_finder_spec.rb} +3 -1
- data/spec/spec_helper.rb +19 -52
- data/spec/support/filters.rb +10 -0
- data/spec/support/queue_subscription_thread_harness.rb +29 -0
- data/spec/support/rabbitmqadmin.rb +74 -0
- metadata +58 -117
- data/lib/euston-rabbitmq/command_handlers/retry_failed_message.rb +0 -29
- data/lib/euston-rabbitmq/event_handlers/message_failure.rb +0 -27
- data/lib/euston-rabbitmq/message_buffer.rb +0 -67
- data/lib/euston-rabbitmq/message_logger.rb +0 -50
- data/lib/euston-rabbitmq/queue.rb +0 -30
- data/lib/euston-rabbitmq/read_model/failed_message.rb +0 -36
- data/lib/euston-rabbitmq/read_model/message_buffer.rb +0 -57
- data/lib/euston-rabbitmq/read_model/message_log.rb +0 -37
- data/spec/command_buffer_spec.rb +0 -69
- data/spec/event_buffer_spec.rb +0 -69
- data/spec/exchange_declaration_spec.rb +0 -28
- data/spec/message_failure_spec.rb +0 -77
- data/spec/mt_safe_queue_subscription_spec.rb +0 -72
- data/spec/safe_queue_subscription_spec.rb +0 -50
- data/spec/support/factories.rb +0 -18
@@ -1,30 +0,0 @@
|
|
1
|
-
module AMQP
|
2
|
-
class Queue
|
3
|
-
include Hollywood
|
4
|
-
|
5
|
-
def safe_subscribe
|
6
|
-
cb = Hollywood.instance_method(:callback).bind self
|
7
|
-
|
8
|
-
self.subscribe :ack => true do |header, body|
|
9
|
-
rt = ::RobustThread.new(:args => [header, body], :label => "#{self.name} queue subscriber") do |header, body|
|
10
|
-
begin
|
11
|
-
message = ::ActiveSupport::JSON.decode body
|
12
|
-
message.recursive_symbolize_keys!
|
13
|
-
|
14
|
-
begin
|
15
|
-
cb.call :message_received, message
|
16
|
-
header.ack
|
17
|
-
rescue Euston::EventStore::ConcurrencyError
|
18
|
-
header.reject :requeue => true
|
19
|
-
rescue StandardError => e
|
20
|
-
cb.call :message_failed, message, e, header
|
21
|
-
end
|
22
|
-
rescue StandardError => e
|
23
|
-
cb.call :message_decode_failed, body, e
|
24
|
-
header.ack
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
module Euston
|
2
|
-
module RabbitMq
|
3
|
-
module ReadModel
|
4
|
-
class FailedMessage
|
5
|
-
def initialize
|
6
|
-
name = 'failed_messages'
|
7
|
-
|
8
|
-
mongodb = Euston::RabbitMq.event_store_mongodb
|
9
|
-
mongodb.create_collection name unless mongodb.collection_names.include? name
|
10
|
-
|
11
|
-
@collection = mongodb.collection name
|
12
|
-
@collection.ensure_index [ ['failure_timestamp', Mongo::ASCENDING] ], :unique => false, :name => 'failed_messages_failure_timestamp_index'
|
13
|
-
end
|
14
|
-
|
15
|
-
def get_by_id id
|
16
|
-
@collection.find_one({ '_id' => id })
|
17
|
-
end
|
18
|
-
|
19
|
-
def find_all
|
20
|
-
@collection.find({}, { :sort => [ 'failure_timestamp', Mongo::DESCENDING ] })
|
21
|
-
end
|
22
|
-
|
23
|
-
def log_failure failure
|
24
|
-
failure.recursive_stringify_keys!
|
25
|
-
failure['_id'] = failure.delete 'message_id'
|
26
|
-
|
27
|
-
@collection.save(failure, :safe => { :fsync => true })
|
28
|
-
end
|
29
|
-
|
30
|
-
def remove_by_id id
|
31
|
-
@collection.remove({ '_id' => id }, :safe => { :fsync => true })
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module Euston
|
2
|
-
module RabbitMq
|
3
|
-
module ReadModel
|
4
|
-
class MessageBuffer
|
5
|
-
class << self
|
6
|
-
def commands
|
7
|
-
@commands ||= MessageBuffer.new "buffered_commands"
|
8
|
-
end
|
9
|
-
|
10
|
-
def events
|
11
|
-
@events ||= MessageBuffer.new "buffered_events"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize name
|
16
|
-
mongodb = Euston::RabbitMq.event_store_mongodb
|
17
|
-
mongodb.create_collection name unless mongodb.collection_names.include? name
|
18
|
-
|
19
|
-
@collection = mongodb.collection name
|
20
|
-
@collection.ensure_index [ ['next_attempt', Mongo::ASCENDING] ], :unique => false, :name => "#{name}_next_attempt_index"
|
21
|
-
end
|
22
|
-
|
23
|
-
def buffer_new_message message
|
24
|
-
@collection.save({ '_id' => message[:headers][:id],
|
25
|
-
'type' => message[:headers][:type],
|
26
|
-
'next_attempt' => Time.now.to_f,
|
27
|
-
'json' => ::ActiveSupport::JSON.encode(message) }, :safe => { :fsync => true })
|
28
|
-
end
|
29
|
-
|
30
|
-
def find_next_message
|
31
|
-
query = { 'next_attempt' => { '$lte' => Time.now.to_f } }
|
32
|
-
@collection.find_one(query)
|
33
|
-
end
|
34
|
-
|
35
|
-
def find_due_messages
|
36
|
-
query = { 'next_attempt' => { '$lte' => Time.now.to_f } }
|
37
|
-
order = [ 'next_attempt', Mongo::ASCENDING ]
|
38
|
-
|
39
|
-
@collection.find(query).sort(order)
|
40
|
-
end
|
41
|
-
|
42
|
-
def get_by_id id
|
43
|
-
@collection.find_one({ '_id' => id })
|
44
|
-
end
|
45
|
-
|
46
|
-
def set_next_attempt message
|
47
|
-
@collection.update({ '_id' => message['_id'] },
|
48
|
-
{ '$set' => { 'next_attempt' => Time.now.to_f + 10 } }, :safe => { :fsync => true })
|
49
|
-
end
|
50
|
-
|
51
|
-
def remove_published_message id
|
52
|
-
@collection.remove({ '_id' => id }, :safe => { :fsync => true })
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Euston
|
2
|
-
module RabbitMq
|
3
|
-
module ReadModel
|
4
|
-
class MessageLog
|
5
|
-
class << self
|
6
|
-
def commands
|
7
|
-
@commands ||= MessageLog.new "command_log"
|
8
|
-
end
|
9
|
-
|
10
|
-
def events
|
11
|
-
@events ||= MessageLog.new "event_log"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize name
|
16
|
-
mongodb = Euston::RabbitMq.event_store_mongodb
|
17
|
-
mongodb.create_collection name unless mongodb.collection_names.include? name
|
18
|
-
|
19
|
-
@collection = mongodb.collection name
|
20
|
-
@collection.ensure_index [ ['timestamp', Mongo::ASCENDING] ], :unique => false, :name => "#{name}_timestamp_index"
|
21
|
-
end
|
22
|
-
|
23
|
-
def find_all
|
24
|
-
@collection.find({}, { :sort => [ 'timestamp', Mongo::DESCENDING ] })
|
25
|
-
end
|
26
|
-
|
27
|
-
def log_new_message message
|
28
|
-
@collection.save({ '_id' => message[:headers][:id],
|
29
|
-
'type' => message[:headers][:type],
|
30
|
-
'version' => message[:headers][:version],
|
31
|
-
'timestamp' => Time.now.to_f,
|
32
|
-
'json' => ::ActiveSupport::JSON.encode(message) }, :safe => { :fsync => true })
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/spec/command_buffer_spec.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
-
|
3
|
-
unless RUBY_PLATFORM == 'java'
|
4
|
-
describe 'command buffer' do
|
5
|
-
include EustonRmqSpec
|
6
|
-
|
7
|
-
context 'the command buffer is created' do
|
8
|
-
include Euston::RabbitMq::Queues
|
9
|
-
|
10
|
-
amqp_before do
|
11
|
-
@buffer = Euston::RabbitMq::MessageBuffer.new @channel, :commands
|
12
|
-
@queue = get_queue @channel, :commands_buffer
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'declares the commands buffer queue' do
|
16
|
-
done(0.5) {
|
17
|
-
@queue.name.should == 'commands_buffer'
|
18
|
-
@queue.opts.should include(:durable => true)
|
19
|
-
}
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'binds the command buffer queue to the commands exchange with a wildcard routing key' do
|
23
|
-
received = []
|
24
|
-
data = Factory.command
|
25
|
-
|
26
|
-
delayed(1) {
|
27
|
-
exchange = @channel.find_exchange 'commands'
|
28
|
-
@queue.when(:message_received => ->(message) { received << message })
|
29
|
-
exchange.publish ::ActiveSupport::JSON.encode(data), :routing_key => "commands.#{Euston.uuid.generate}"
|
30
|
-
}
|
31
|
-
|
32
|
-
done(2) {
|
33
|
-
received.should have(1).item
|
34
|
-
received.first.should == data
|
35
|
-
}
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
context 'a command is pushed into the buffer by client code' do
|
40
|
-
amqp_before :each do
|
41
|
-
@buffer = Euston::RabbitMq::MessageBuffer.new @channel, :commands
|
42
|
-
@command = Factory.command
|
43
|
-
@buffer.push @command
|
44
|
-
@read_model = Euston::RabbitMq::ReadModel::MessageBuffer.new 'buffered_commands'
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'writes the command to persistent storage' do
|
48
|
-
@read_model.find_due_messages.to_a.should have(1).items
|
49
|
-
|
50
|
-
due_command = @read_model.find_due_messages.first
|
51
|
-
due_command['_id'].should == @command[:headers][:id]
|
52
|
-
due_command['type'].should == @command[:headers][:type]
|
53
|
-
due_command['json'].should == ::ActiveSupport::JSON.encode(@command)
|
54
|
-
|
55
|
-
done(0.3)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'removes the command from the buffer storage when it is obtained from the buffer queue' do
|
59
|
-
delayed(1) {
|
60
|
-
@buffer.dispatch_due_messages
|
61
|
-
}
|
62
|
-
|
63
|
-
done(3) {
|
64
|
-
@read_model.get_by_id(@command[:headers][:id]).should be_nil
|
65
|
-
}
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/spec/event_buffer_spec.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
-
|
3
|
-
unless RUBY_PLATFORM == 'java'
|
4
|
-
describe 'event buffer' do
|
5
|
-
include EustonRmqSpec
|
6
|
-
|
7
|
-
context 'the event buffer is created' do
|
8
|
-
include Euston::RabbitMq::Queues
|
9
|
-
|
10
|
-
amqp_before do
|
11
|
-
@buffer = Euston::RabbitMq::MessageBuffer.new @channel, :events
|
12
|
-
@queue = get_queue @channel, :events_buffer
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'declares the events buffer queue' do
|
16
|
-
done(0.5) {
|
17
|
-
@queue.name.should == 'events_buffer'
|
18
|
-
@queue.opts.should include(:durable => true)
|
19
|
-
}
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'binds the event buffer queue to the events exchange with a wildcard routing key' do
|
23
|
-
received = []
|
24
|
-
data = Factory.command
|
25
|
-
|
26
|
-
delayed(0.3) {
|
27
|
-
exchange = @channel.find_exchange 'events'
|
28
|
-
@queue.when(:message_received => ->(message) { received << message })
|
29
|
-
exchange.publish ::ActiveSupport::JSON.encode(data), :routing_key => "events.#{Euston.uuid.generate}"
|
30
|
-
}
|
31
|
-
|
32
|
-
done(2) {
|
33
|
-
received.should have(1).item
|
34
|
-
received.first.should == data
|
35
|
-
}
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
context 'an event is pushed into the buffer by client code' do
|
40
|
-
amqp_before :each do
|
41
|
-
@buffer = Euston::RabbitMq::MessageBuffer.new @channel, :events
|
42
|
-
@command = Factory.command
|
43
|
-
@buffer.push @command
|
44
|
-
@read_model = Euston::RabbitMq::ReadModel::MessageBuffer.new 'buffered_events'
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'writes the event to persistent storage' do
|
48
|
-
@read_model.find_due_messages.to_a.should have(1).items
|
49
|
-
|
50
|
-
due_command = @read_model.find_due_messages.first
|
51
|
-
due_command['_id'].should == @command[:headers][:id]
|
52
|
-
due_command['type'].should == @command[:headers][:type]
|
53
|
-
due_command['json'].should == ::ActiveSupport::JSON.encode(@command)
|
54
|
-
|
55
|
-
done(0.3)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'removes the event from the buffer storage when it is obtained from the buffer queue' do
|
59
|
-
delayed(1) {
|
60
|
-
@buffer.dispatch_due_messages
|
61
|
-
}
|
62
|
-
|
63
|
-
done(3) {
|
64
|
-
@read_model.get_by_id(@command[:headers][:id]).should be_nil
|
65
|
-
}
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
-
|
3
|
-
unless RUBY_PLATFORM == 'java'
|
4
|
-
describe 'exchange declaration' do
|
5
|
-
include EustonRmqSpec
|
6
|
-
|
7
|
-
context 'declaring the commands exchange' do
|
8
|
-
include Euston::RabbitMq::Exchanges
|
9
|
-
it 'declares the commands exchange' do
|
10
|
-
exchange = get_exchange @channel, :commands
|
11
|
-
|
12
|
-
done(0.3) {
|
13
|
-
exchange.name.should == 'commands'
|
14
|
-
exchange.should be_durable
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'declares the events exchange' do
|
19
|
-
exchange = get_exchange @channel, :events
|
20
|
-
|
21
|
-
done(0.3) {
|
22
|
-
exchange.name.should == 'events'
|
23
|
-
exchange.should be_durable
|
24
|
-
}
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
-
|
3
|
-
unless RUBY_PLATFORM == 'java'
|
4
|
-
module MessageFailureTracking
|
5
|
-
module FailingCommandHandlers
|
6
|
-
class DeliverWidget
|
7
|
-
class << self
|
8
|
-
attr_accessor :fail_next
|
9
|
-
end
|
10
|
-
|
11
|
-
include Euston::CommandHandler
|
12
|
-
|
13
|
-
version 1 do |headers, command|
|
14
|
-
if self.class.fail_next
|
15
|
-
raise 'User-generated failure'
|
16
|
-
else
|
17
|
-
publish headers, command
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe 'message failure tracking' do
|
25
|
-
include EustonRmqSpec
|
26
|
-
include Euston::RabbitMq::Exchanges
|
27
|
-
include Euston::RabbitMq::Queues
|
28
|
-
|
29
|
-
amqp_before do
|
30
|
-
@exchange = get_exchange @channel, :commands
|
31
|
-
@handler = ::MessageFailureTracking::FailingCommandHandlers::DeliverWidget
|
32
|
-
|
33
|
-
command_handlers = Euston::RabbitMq::CommandHandlerBindings.new @channel
|
34
|
-
command_handlers.add_namespace Euston::RabbitMq::CommandHandlers
|
35
|
-
command_handlers.add_namespace ::MessageFailureTracking::FailingCommandHandlers
|
36
|
-
command_handlers.finalize_bindings
|
37
|
-
|
38
|
-
event_handlers = Euston::RabbitMq::EventHandlerBindings.new @channel
|
39
|
-
event_handlers.add_namespace Euston::RabbitMq::EventHandlers
|
40
|
-
event_handlers.finalize_bindings
|
41
|
-
|
42
|
-
@command = { :headers => { :id => Euston.uuid.generate,
|
43
|
-
:type => 'deliver_widget',
|
44
|
-
:version => 1 },
|
45
|
-
:body => { :id => Euston.uuid.generate,
|
46
|
-
:name => 'Bill Smith' } }
|
47
|
-
end
|
48
|
-
|
49
|
-
def publish
|
50
|
-
json = ::ActiveSupport::JSON.encode(@command)
|
51
|
-
opts = default_publish_options.merge(:routing_key => 'commands.deliver_widget')
|
52
|
-
delayed(0.3) { @exchange.publish json, opts }
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'a failing command is published' do
|
56
|
-
it 'moves the message to the failed-messages queue' do
|
57
|
-
@handler.fail_next = true
|
58
|
-
received = 0
|
59
|
-
|
60
|
-
delayed(0.3) {
|
61
|
-
@queue = get_queue @channel, :message_failure
|
62
|
-
@queue.when(:message_received => ->(message) { received = received + 1 })
|
63
|
-
|
64
|
-
publish
|
65
|
-
}
|
66
|
-
|
67
|
-
done(3) {
|
68
|
-
received.should == 1
|
69
|
-
}
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
context 'a failed message is retried' do
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
-
|
3
|
-
if RUBY_PLATFORM == 'java'
|
4
|
-
require 'spec_helper'
|
5
|
-
|
6
|
-
describe 'message-failure-aware queue subscription' do
|
7
|
-
include EustonRmqSpec
|
8
|
-
|
9
|
-
PUBLISH_OPTS = { :immediate => false,
|
10
|
-
:mandatory => true,
|
11
|
-
:persistent => true,
|
12
|
-
:routing_key => 'messages.xyz' }
|
13
|
-
|
14
|
-
before :each do
|
15
|
-
@queue = @exchange = nil
|
16
|
-
@exchange = @channel.topic "test-#{Euston.uuid.generate}", :durable => true, :nowait => false
|
17
|
-
@queue = @channel.queue "test-#{Euston.uuid.generate}", :durable => true, :nowait => false
|
18
|
-
|
19
|
-
@queue.bind @exchange, :routing_key => 'messages.#'
|
20
|
-
@th = Thread.new do
|
21
|
-
Thread.current[:failed_count] = 0
|
22
|
-
Thread.current[:succeed_count] = 0
|
23
|
-
|
24
|
-
def on_message_decode_failed(message, error)
|
25
|
-
Thread.current[:failed_count] += 1
|
26
|
-
end
|
27
|
-
|
28
|
-
def on_message_failed(message, error, msg)
|
29
|
-
Thread.current[:failed_count] += 1
|
30
|
-
end
|
31
|
-
|
32
|
-
def on_message_received(message)
|
33
|
-
if message[:a] == 'oops'
|
34
|
-
raise 'Something bad happened'
|
35
|
-
else
|
36
|
-
Thread.current[:succeed_count] += 1
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
@queue.when(:message_decode_failed => method(:on_message_decode_failed),
|
41
|
-
:message_failed => method(:on_message_failed),
|
42
|
-
:message_received => method(:on_message_received))
|
43
|
-
|
44
|
-
@queue.safe_subscribe
|
45
|
-
end
|
46
|
-
|
47
|
-
@th.abort_on_exception = true
|
48
|
-
end
|
49
|
-
|
50
|
-
after :each do
|
51
|
-
@th.kill
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'adds a failure-aware subscription function to the amqp queue object' do
|
55
|
-
@queue.class.should == AMQP::Queue
|
56
|
-
@queue.should respond_to(:safe_subscribe)
|
57
|
-
|
58
|
-
@exchange.publish ' *897d- -', PUBLISH_OPTS
|
59
|
-
sleep 0.25
|
60
|
-
@th[:failed_count].should == 1
|
61
|
-
|
62
|
-
jsn = JSON.generate({ :a => :b })
|
63
|
-
@exchange.publish jsn, PUBLISH_OPTS
|
64
|
-
sleep 0.25
|
65
|
-
@th[:succeed_count].should == 1
|
66
|
-
|
67
|
-
@exchange.publish JSON.generate({ :a => 'oops' }), PUBLISH_OPTS
|
68
|
-
sleep 0.25
|
69
|
-
@th[:failed_count].should == 2
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|