danielsdeleo-moqueue 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +57 -0
- data/Rakefile +25 -0
- data/VERSION.yml +4 -0
- data/lib/moqueue.rb +15 -0
- data/lib/moqueue/fibers18.rb +57 -0
- data/lib/moqueue/matchers.rb +74 -0
- data/lib/moqueue/mock_broker.rb +44 -0
- data/lib/moqueue/mock_exchange.rb +114 -0
- data/lib/moqueue/mock_headers.rb +31 -0
- data/lib/moqueue/mock_queue.rb +126 -0
- data/lib/moqueue/object_methods.rb +31 -0
- data/lib/moqueue/overloads.rb +46 -0
- data/moqueue.gemspec +29 -0
- data/spec/examples/ack_spec.rb +60 -0
- data/spec/examples/basic_usage_spec.rb +101 -0
- data/spec/examples/example_helper.rb +16 -0
- data/spec/examples/logger_spec.rb +159 -0
- data/spec/examples/ping_pong_spec.rb +50 -0
- data/spec/examples/stocks_spec.rb +64 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/unit/matchers_spec.rb +92 -0
- data/spec/unit/mock_broker_spec.rb +34 -0
- data/spec/unit/mock_exchange_spec.rb +102 -0
- data/spec/unit/mock_headers_spec.rb +22 -0
- data/spec/unit/mock_queue_spec.rb +141 -0
- data/spec/unit/moqueue_spec.rb +9 -0
- data/spec/unit/object_methods_spec.rb +49 -0
- data/spec/unit/overloads_spec.rb +48 -0
- metadata +87 -0
data/README.rdoc
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
= Moqueue
|
2
|
+
Moqueue is a library for mocking the various objects that make up the ruby AMQP[http://github.com/tmm1/amqp] library. It allows you to use the AMQP library naturally and test your code easily without running an AMQP broker. If you want a higher level of control, you can use your favorite mocking and stubbing library to modify individual calls to MQ.queue and the like so that they return Moqueue's mock up versions. If you want to go all-in, you can tell Moqueue to overload the MQ and AMQP. This allows you to use MQ and AMQP as normal, while Moqueue works behind the scenes to wire everything together.
|
3
|
+
|
4
|
+
= Getting started
|
5
|
+
|
6
|
+
require "moqueue"
|
7
|
+
overload_amqp
|
8
|
+
|
9
|
+
mq = MQ.new
|
10
|
+
=> #<MQ:0x1197ae8>
|
11
|
+
|
12
|
+
queue = mq.queue("mocktacular")
|
13
|
+
=> #<Moqueue::MockQueue:0x1194550 @name="mocktacular">
|
14
|
+
|
15
|
+
topic = mq.topic("lolz")
|
16
|
+
=> #<Moqueue::MockExchange:0x11913dc @topic="lolz">
|
17
|
+
|
18
|
+
queue.bind(topic, :key=> "cats.*")
|
19
|
+
=> #<Moqueue::MockQueue:0x1194550 @name="mocktacular">
|
20
|
+
|
21
|
+
queue.subscribe {|header, msg| puts [header.routing_key, msg]}
|
22
|
+
=> nil
|
23
|
+
|
24
|
+
topic.publish("eatin ur foodz", :key => "cats.inUrFridge")
|
25
|
+
# cats.inUrFridge
|
26
|
+
# eatin ur foodz
|
27
|
+
|
28
|
+
Note that in this example, we didn't have to deal with <tt>AMQP.start</tt> or <tt>EM.run</tt>. This should be ample evidence that you should run higher level tests without any mocks or stubs so you can be sure everything works with real MQ objects. With that said, <tt>#overload_amqp</tt> does overload the <tt>AMQP.start</tt> method, so you can use Moqueue for mid-level testing if desired. Have a look at the spec/examples directory to see Moqueue running some of AMQP's examples in overload mode for more demonstration of this.
|
29
|
+
|
30
|
+
= Custom Rspec Matchers
|
31
|
+
For Test::Unit users, Moqueue's default syntax should be a good fit with <tt>assert()</tt>:
|
32
|
+
assert(queue.received_message?("eatin ur foodz"))
|
33
|
+
Rspec users will probably want something a bit more natural language-y. You got it:
|
34
|
+
queue.should have_received("a message")
|
35
|
+
queue.should have_ack_for("a different message")
|
36
|
+
|
37
|
+
= What's Working? What's Not?
|
38
|
+
As you can tell from the example above, quite a bit is working. This includes direct exchanges where you call <tt>#publish</tt> and <tt>#subscribe</tt> on the same queue, acknowledgements, topic exchanges, and fanout exchanges.
|
39
|
+
|
40
|
+
What's not working:
|
41
|
+
* RPC exchanges.
|
42
|
+
* The routing key matching algorithm works for common cases, including "*" and "#" wildcards in the binding key. If you need anything more complicated than that, Moqueue is not guaranteed to do the right thing.
|
43
|
+
* Receiving acks when using topic exchanges works only if you subscribe before publishing.
|
44
|
+
|
45
|
+
There are some things that Moqueue may never be able to do. As one prominent example, for queues that are configured to expect acknowledgements (the <tt>:ack=>true</tt> option), the behavior on shutdown is not emulated correctly.
|
46
|
+
|
47
|
+
== TODOs
|
48
|
+
* Mocha-style expectation syntax, i.e. <tt>queue.expects_message("yessss").with_routing_key("awesome.yesItIs")</tt>
|
49
|
+
* Support for RPC exchanges
|
50
|
+
* Make it pretty on the inside.
|
51
|
+
|
52
|
+
== Moar
|
53
|
+
I wrote an introductory post on my blog, it's probably the best source of high-level discussion right now. Visit: http://kallistec.com/2009/06/21/introducing-moqueue/
|
54
|
+
|
55
|
+
If you prefer code over drivel, look at the specs under spec/examples. There you'll find some of the examples from the amqp library running completely Moqueue-ified; the basic_usage_spec.rb shows some lower-level use.
|
56
|
+
|
57
|
+
As always, you're invited to git yer fork on if you want to work on any of these. If you find a bug that you can't source or want to send love or hate mail, you can contact me directly at dan@kallistec.com
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec/rake/spectask"
|
2
|
+
|
3
|
+
task :default => :spec
|
4
|
+
|
5
|
+
desc "Run all of the specs"
|
6
|
+
Spec::Rake::SpecTask.new do |t|
|
7
|
+
t.spec_opts = ['--options', "\"spec/spec.opts\""]
|
8
|
+
t.fail_on_error = false
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |s|
|
14
|
+
s.name = "moqueue"
|
15
|
+
s.summary = "Mocktacular Companion to AMQP Library. Happy TATFTing!"
|
16
|
+
s.email = "dan@kallistec.com"
|
17
|
+
s.homepage = "http://github.com/danielsdeleo/moqueue"
|
18
|
+
s.description = "Mocktacular Companion to AMQP Library. Happy TATFTing!"
|
19
|
+
s.authors = ["Daniel DeLeo"]
|
20
|
+
s.files = FileList["[A-Za-z]*", "{lib,spec}/**/*"]
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
24
|
+
end
|
25
|
+
|
data/VERSION.yml
ADDED
data/lib/moqueue.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
unless defined?(MOQUEUE_ROOT)
|
2
|
+
MOQUEUE_ROOT = File.dirname(__FILE__) + "/"
|
3
|
+
end
|
4
|
+
require MOQUEUE_ROOT + "moqueue/fibers18"
|
5
|
+
|
6
|
+
require MOQUEUE_ROOT + "moqueue/mock_exchange"
|
7
|
+
require MOQUEUE_ROOT + "moqueue/mock_queue"
|
8
|
+
require MOQUEUE_ROOT + "moqueue/mock_headers"
|
9
|
+
require MOQUEUE_ROOT + "moqueue/mock_broker"
|
10
|
+
|
11
|
+
require MOQUEUE_ROOT + "moqueue/object_methods"
|
12
|
+
require MOQUEUE_ROOT + "moqueue/matchers"
|
13
|
+
|
14
|
+
module Moqueue
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Courtesy Aman Gupta's (original) em-spec library
|
2
|
+
unless defined? Fiber
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
class FiberError < StandardError; end
|
6
|
+
|
7
|
+
class Fiber
|
8
|
+
def initialize
|
9
|
+
raise ArgumentError, 'new Fiber requires a block' unless block_given?
|
10
|
+
|
11
|
+
@yield = Queue.new
|
12
|
+
@resume = Queue.new
|
13
|
+
|
14
|
+
@thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
|
15
|
+
@thread.abort_on_exception = true
|
16
|
+
@thread[:fiber] = self
|
17
|
+
end
|
18
|
+
attr_reader :thread
|
19
|
+
|
20
|
+
def alive?
|
21
|
+
@thread.alive?
|
22
|
+
end
|
23
|
+
|
24
|
+
def resume *args
|
25
|
+
raise FiberError, 'dead fiber called' unless @thread.alive?
|
26
|
+
raise FiberError, 'double resume' if @thread == Thread.current
|
27
|
+
@resume.push(args)
|
28
|
+
result = @yield.pop
|
29
|
+
result.size > 1 ? result : result.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def resume!
|
33
|
+
@resume.push []
|
34
|
+
end
|
35
|
+
|
36
|
+
def yield *args
|
37
|
+
@yield.push(args)
|
38
|
+
result = @resume.pop
|
39
|
+
result.size > 1 ? result : result.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.yield *args
|
43
|
+
raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
|
44
|
+
fiber.yield(*args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.current
|
48
|
+
Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}>"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
else
|
56
|
+
require 'fiber' unless Fiber.respond_to?(:current)
|
57
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Moqueue
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
class HasReceived
|
5
|
+
|
6
|
+
def initialize(expected_msg)
|
7
|
+
@expected_msg = expected_msg
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(queue)
|
11
|
+
if queue.respond_to?(:received_message?)
|
12
|
+
@queue = queue
|
13
|
+
@queue.received_message?(@expected_msg)
|
14
|
+
else
|
15
|
+
raise NoMethodError,
|
16
|
+
"Grrr. you can't use ``should have_received_message'' on #{queue.inspect} " +
|
17
|
+
"because it doesn't respond_to :received_message?"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def failure_message_for_should
|
22
|
+
"expected #{@queue.inspect} to have received message ``#{@expected_msg}''"
|
23
|
+
end
|
24
|
+
|
25
|
+
def failure_message_for_should_not
|
26
|
+
"expected #{@queue.inspect} to not have received message ``#{@expected_msg}''"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class HasAcked
|
32
|
+
|
33
|
+
def initialize(msg_expecting_ack)
|
34
|
+
@msg_expecting_ack = msg_expecting_ack
|
35
|
+
end
|
36
|
+
|
37
|
+
def matches?(queue_or_exchange)
|
38
|
+
if queue_or_exchange.respond_to?(:received_ack_for_message?)
|
39
|
+
@queue_or_exchange = queue_or_exchange
|
40
|
+
@queue_or_exchange.received_ack_for_message?(@msg_expecting_ack)
|
41
|
+
else
|
42
|
+
raise NoMethodError,
|
43
|
+
"Grrr. you can't use ``should have_received_ack_for'' on #{queue_or_exchange.inspect} " +
|
44
|
+
"because it doesn't respond_to :received_ack_for_message?"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def failure_message_for_should
|
49
|
+
"expected #{@queue_or_exchange.inspect} to have received an ack for the message ``#{@msg_expecting_ack}''"
|
50
|
+
end
|
51
|
+
|
52
|
+
def failure_message_for_should_not
|
53
|
+
"expected #{@queue_or_exchange.inspect} to not have received an ack for the message ``#{@msg_expecting_ack}''"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def have_received_message(expected_msg)
|
58
|
+
HasReceived.new(expected_msg)
|
59
|
+
end
|
60
|
+
|
61
|
+
def have_received_ack_for(expected_msg)
|
62
|
+
HasAcked.new(expected_msg)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :have_received, :have_received_message
|
66
|
+
alias_method :have_ack_for, :have_received_ack_for
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if defined?(::Spec::Runner)
|
71
|
+
Spec::Runner.configure do |config|
|
72
|
+
config.include(::Moqueue::Matchers)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Moqueue
|
4
|
+
class MockBroker
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_reader :registered_queues
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
reset!
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset!
|
14
|
+
@registered_queues = {}
|
15
|
+
@registered_topic_exchanges = {}
|
16
|
+
@registered_fanout_exchanges = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_queue(name)
|
20
|
+
@registered_queues[name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_queue(queue)
|
24
|
+
@registered_queues[queue.name] = queue
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_topic_exchange(exchange)
|
28
|
+
@registered_topic_exchanges[exchange.topic] = exchange
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_topic_exchange(topic)
|
32
|
+
@registered_topic_exchanges[topic]
|
33
|
+
end
|
34
|
+
|
35
|
+
def register_fanout_exchange(exchange)
|
36
|
+
@registered_fanout_exchanges[exchange.fanout] = exchange
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_fanout_exchange(fanout_name)
|
40
|
+
@registered_fanout_exchanges[fanout_name]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Moqueue
|
2
|
+
|
3
|
+
class MockExchange
|
4
|
+
attr_reader :topic, :fanout
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def new(opts={})
|
9
|
+
if opts[:topic] && topic_exchange = MockBroker.instance.find_topic_exchange(opts[:topic])
|
10
|
+
return topic_exchange
|
11
|
+
end
|
12
|
+
|
13
|
+
if opts[:fanout] && fanout = MockBroker.instance.find_fanout_exchange(opts[:fanout])
|
14
|
+
return fanout
|
15
|
+
end
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(opts={})
|
23
|
+
if @topic = opts[:topic]
|
24
|
+
MockBroker.instance.register_topic_exchange(self)
|
25
|
+
elsif @fanout = opts[:fanout]
|
26
|
+
MockBroker.instance.register_fanout_exchange(self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def attached_queues
|
31
|
+
@attached_queues ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def acked_messages
|
35
|
+
@acked_messages ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def attach_queue(queue, opts={})
|
39
|
+
if topic
|
40
|
+
attached_queues << [queue, BindingKey.new(opts[:key])]
|
41
|
+
else
|
42
|
+
attached_queues << queue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def publish(message, opts={})
|
47
|
+
require_routing_key(opts) if topic
|
48
|
+
matching_queues(opts).each do |q|
|
49
|
+
ack_message(q.receive(message, prepare_header_opts(opts)))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def received_ack_for_message?(message)
|
54
|
+
acked_messages.include?(message)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def ack_message(message)
|
60
|
+
acked_messages << message if message
|
61
|
+
end
|
62
|
+
|
63
|
+
def routing_keys_match?(binding_key, message_key)
|
64
|
+
BindingKey.new(binding_key).matches?(message_key)
|
65
|
+
end
|
66
|
+
|
67
|
+
def matching_queues(opts={})
|
68
|
+
return attached_queues unless topic
|
69
|
+
attached_queues.map {|q, binding| binding.matches?(opts[:key]) ? q : nil}.compact
|
70
|
+
end
|
71
|
+
|
72
|
+
def prepare_header_opts(opts={})
|
73
|
+
header_opts = opts.dup
|
74
|
+
if routing_key = header_opts.delete(:key)
|
75
|
+
header_opts[:routing_key] = routing_key
|
76
|
+
end
|
77
|
+
header_opts
|
78
|
+
end
|
79
|
+
|
80
|
+
def require_routing_key(opts={})
|
81
|
+
unless opts.has_key?(:key)
|
82
|
+
raise ArgumentError, "you must provide a key when publishing to a topic exchange"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
public
|
87
|
+
|
88
|
+
class BindingKey
|
89
|
+
attr_reader :key
|
90
|
+
|
91
|
+
def initialize(key_string)
|
92
|
+
@key = key_string.to_s.split(".")
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
other.respond_to?(:key) && other.key == @key
|
97
|
+
end
|
98
|
+
|
99
|
+
def matches?(message_key)
|
100
|
+
message_key, binding_key = message_key.split("."), key.dup
|
101
|
+
|
102
|
+
match = true
|
103
|
+
while match
|
104
|
+
binding_token, message_token = binding_key.shift, message_key.shift
|
105
|
+
break if (binding_token.nil? && message_token.nil?) || (binding_token == "#")
|
106
|
+
match = ((binding_token == message_token) || (binding_token == '*') || (message_token == '*'))
|
107
|
+
end
|
108
|
+
match
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Moqueue
|
2
|
+
|
3
|
+
class MockHeaders
|
4
|
+
attr_accessor :size, :weight
|
5
|
+
|
6
|
+
def initialize(properties={})
|
7
|
+
@properties = properties
|
8
|
+
end
|
9
|
+
|
10
|
+
def ack
|
11
|
+
@received_ack = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def received_ack?
|
15
|
+
@received_ack || false
|
16
|
+
end
|
17
|
+
|
18
|
+
def properties
|
19
|
+
@properties
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_frame
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing method, *args, &blk
|
27
|
+
@properties.has_key?(method) ? @properties[method] : super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Moqueue
|
2
|
+
|
3
|
+
class DoubleSubscribeError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class MockQueue
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def new(name)
|
12
|
+
if existing_queue = MockBroker.instance.find_queue(name)
|
13
|
+
return existing_queue
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(name)
|
21
|
+
@name = name
|
22
|
+
MockBroker.instance.register_queue(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def subscribe(opts={}, &block)
|
26
|
+
if @subscribe_block
|
27
|
+
raise DoubleSubscribeError, "you can't subscribe to the same queue twice"
|
28
|
+
end
|
29
|
+
@subscribe_block = block
|
30
|
+
@ack_msgs = opts[:ack] || false
|
31
|
+
process_unhandled_messages
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive(message, header_opts={})
|
35
|
+
if callback = message_handler_callback
|
36
|
+
headers = MockHeaders.new(header_opts)
|
37
|
+
callback.call(*(callback.arity == 1 ? [message] : [headers, message]))
|
38
|
+
received_messages << message
|
39
|
+
@ack_msgs && headers.received_ack? ? message : nil
|
40
|
+
else
|
41
|
+
receive_message_later(message, header_opts)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def received_message?(message_content)
|
46
|
+
received_messages.include?(message_content)
|
47
|
+
end
|
48
|
+
|
49
|
+
def unsubscribe
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def received_ack_for_message?(message_content)
|
54
|
+
acked_messages.include?(message_content)
|
55
|
+
end
|
56
|
+
|
57
|
+
def publish(message)
|
58
|
+
if message_handler_callback
|
59
|
+
real_publish(message)
|
60
|
+
else
|
61
|
+
deferred_publishing_fibers << Fiber.new do
|
62
|
+
real_publish(message)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def bind(exchange, key=nil)
|
68
|
+
exchange.attach_queue(self, key)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def received_messages
|
73
|
+
@received_messages ||= []
|
74
|
+
end
|
75
|
+
|
76
|
+
def acked_messages
|
77
|
+
@acked_messages ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_callback(*args)
|
81
|
+
callback = message_handler_callback
|
82
|
+
callback.call(*(callback.arity == 1 ? [args.first] : args))
|
83
|
+
end
|
84
|
+
|
85
|
+
def callback_defined?
|
86
|
+
!!message_handler_callback
|
87
|
+
end
|
88
|
+
|
89
|
+
# configures a do-nothing subscribe block to force
|
90
|
+
# received messages to be processed and stored in
|
91
|
+
# #received_messages
|
92
|
+
def null_subscribe
|
93
|
+
subscribe {|msg| nil}
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def receive_message_later(message, header_opts)
|
100
|
+
deferred_publishing_fibers << Fiber.new do
|
101
|
+
self.receive(message, header_opts)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def deferred_publishing_fibers
|
106
|
+
@deferred_publishing_fibers ||= []
|
107
|
+
end
|
108
|
+
|
109
|
+
def message_handler_callback
|
110
|
+
@subscribe_block || @pop_block || false
|
111
|
+
end
|
112
|
+
|
113
|
+
def process_unhandled_messages
|
114
|
+
while fiber = deferred_publishing_fibers.shift
|
115
|
+
fiber.resume
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def real_publish(message)
|
120
|
+
response = receive(message)
|
121
|
+
acked_messages << response if response
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|