hot_bunnies 1.3.8-java → 1.4.0-java
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.
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/ChangeLog.md +33 -0
- data/README.md +17 -1
- data/lib/ext/rabbitmq-client.jar +0 -0
- data/lib/hot_bunnies/queue.rb +178 -67
- data/lib/hot_bunnies/version.rb +1 -1
- data/lib/hot_bunnies.rb +17 -8
- data/spec/integration/basic_consume_spec.rb +99 -0
- data/spec/integration/connection_spec.rb +8 -1
- data/spec/integration/error_handling_by_consumers_spec.rb +97 -0
- data/spec/integration/message_metadata_access_spec.rb +94 -0
- data/spec/spec_helper.rb +3 -1
- metadata +5 -2
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/ChangeLog.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Changes Between 1.3.0 and 1.4.0
|
2
|
+
|
3
|
+
## RabbitMQ Java Client Upgrade
|
4
|
+
|
5
|
+
Hot Bunnies now uses RabbitMQ Java client 2.8.7.
|
6
|
+
|
7
|
+
|
8
|
+
## TLS Support
|
9
|
+
|
10
|
+
`HotBunnies.connect` now supports a new `:tls` option:
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
HotBunnies.connect(:tls => true)
|
14
|
+
|
15
|
+
HotBunnies.connect(:tls => "SSLv3")
|
16
|
+
HotBunnies.connect(:tls => "SSLv2")
|
17
|
+
|
18
|
+
HotBunnies.connect(:tls => "SSLv3", :trust_manager => custom_trust_manager)
|
19
|
+
```
|
20
|
+
|
21
|
+
|
22
|
+
## Consumer Back Pressure Improvements
|
23
|
+
|
24
|
+
* The async consumer will not attempt to add tasks when its executor is shutting down.
|
25
|
+
|
26
|
+
* The blocking consumer got a buffer size option that makes it create a bounded blocking queue instead of an unbounded.
|
27
|
+
|
28
|
+
|
29
|
+
## Consumer Improvements
|
30
|
+
|
31
|
+
`HotBunnies::Queue#subscribe` is now more resilient to exceptions and uses a new
|
32
|
+
executor task for each delivery. When a consumer is cancelled, any remaining messages
|
33
|
+
will be delivered instead of being ignored.
|
data/README.md
CHANGED
@@ -25,6 +25,22 @@ Hot Bunnies is not
|
|
25
25
|
* A cure for cancer
|
26
26
|
|
27
27
|
|
28
|
+
## Installation, Dependency
|
29
|
+
|
30
|
+
### With Rubygems
|
31
|
+
|
32
|
+
gem install hot_bunnies
|
33
|
+
|
34
|
+
### With Bundler
|
35
|
+
|
36
|
+
gem "hot_bunnies", "~> 1.4.0"
|
37
|
+
|
38
|
+
|
39
|
+
## Change Log
|
40
|
+
|
41
|
+
See ChangeLog.md.
|
42
|
+
|
43
|
+
|
28
44
|
## Continuous Integration
|
29
45
|
|
30
46
|
[](http://travis-ci.org/ruby-amqp/hot_bunnies)
|
@@ -39,4 +55,4 @@ MIT, see LICENSE in the repository root
|
|
39
55
|
|
40
56
|
## Copyright
|
41
57
|
|
42
|
-
Theo Hultberg, 2011.
|
58
|
+
Theo Hultberg, Michael Klishin, 2011-2012.
|
data/lib/ext/rabbitmq-client.jar
CHANGED
Binary file
|
data/lib/hot_bunnies/queue.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
module JavaConcurrent
|
4
|
+
java_import 'java.lang.Thread'
|
5
|
+
java_import 'java.lang.InterruptedException'
|
6
|
+
java_import 'java.util.concurrent.Executors'
|
7
|
+
java_import 'java.util.concurrent.LinkedBlockingQueue'
|
8
|
+
java_import 'java.util.concurrent.ArrayBlockingQueue'
|
9
|
+
java_import 'java.util.concurrent.TimeUnit'
|
10
|
+
java_import 'java.util.concurrent.atomic.AtomicBoolean'
|
11
|
+
end
|
12
|
+
|
3
13
|
module HotBunnies
|
4
14
|
class Queue
|
5
15
|
attr_reader :name, :channel
|
@@ -59,6 +69,8 @@ module HotBunnies
|
|
59
69
|
end
|
60
70
|
|
61
71
|
class Subscription
|
72
|
+
include JavaConcurrent
|
73
|
+
|
62
74
|
attr_reader :channel, :queue_name, :consumer_tag
|
63
75
|
|
64
76
|
def initialize(channel, queue_name, options={})
|
@@ -66,28 +78,25 @@ module HotBunnies
|
|
66
78
|
@queue_name = queue_name
|
67
79
|
@ack = options.fetch(:ack, false)
|
68
80
|
|
69
|
-
@cancelled =
|
81
|
+
@cancelled = AtomicBoolean.new(false)
|
70
82
|
end
|
71
83
|
|
72
84
|
def each(options={}, &block)
|
73
|
-
raise 'The subscription already has a message listener' if @
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
@executor.submit { run(&block) }
|
85
|
-
end
|
85
|
+
raise 'The subscription already has a message listener' if @consumer
|
86
|
+
start(create_consumer(options, block))
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
alias_method :each_message, :each
|
90
|
+
|
91
|
+
def start(consumer)
|
92
|
+
@consumer = consumer
|
93
|
+
@consumer_tag = @channel.basic_consume(@queue_name, !@ack, @consumer)
|
94
|
+
@consumer.start
|
86
95
|
end
|
87
96
|
|
88
97
|
def cancel
|
89
98
|
raise 'Can\'t cancel: the subscriber haven\'t received an OK yet' if !self.active?
|
90
|
-
@
|
99
|
+
@consumer.cancel
|
91
100
|
|
92
101
|
# RabbitMQ Java client won't clear consumer_tag from cancelled consumers,
|
93
102
|
# so we have to do this. Sharing consumers
|
@@ -103,7 +112,7 @@ module HotBunnies
|
|
103
112
|
end
|
104
113
|
|
105
114
|
def active?
|
106
|
-
!@cancelled.get && !@
|
115
|
+
!@cancelled.get && !@consumer.nil? && !@consumer.consumer_tag.nil?
|
107
116
|
end
|
108
117
|
|
109
118
|
def shutdown!
|
@@ -111,96 +120,198 @@ module HotBunnies
|
|
111
120
|
@executor.shutdown_now
|
112
121
|
end
|
113
122
|
end
|
123
|
+
alias shut_down! shutdown!
|
114
124
|
|
115
125
|
private
|
116
126
|
|
117
127
|
def maybe_shutdown_executor
|
118
128
|
if @executor && @shut_down_executor
|
119
129
|
@executor.shutdown
|
130
|
+
unless @executor.await_termination(1, TimeUnit::SECONDS)
|
131
|
+
@executor.shutdown_now
|
132
|
+
end
|
120
133
|
end
|
121
134
|
end
|
122
135
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
136
|
+
def create_consumer(options, callback)
|
137
|
+
if options.fetch(:blocking, true)
|
138
|
+
BlockingCallbackConsumer.new(@channel, options[:buffer_size], callback)
|
139
|
+
else
|
140
|
+
if options[:executor]
|
141
|
+
@shut_down_executor = false
|
142
|
+
@executor = options[:executor]
|
143
|
+
else
|
144
|
+
@shut_down_executor = true
|
145
|
+
@executor = Executors.new_single_thread_executor
|
146
|
+
end
|
147
|
+
AsyncCallbackConsumer.new(@channel, callback, @executor)
|
148
|
+
end
|
127
149
|
end
|
128
150
|
end
|
129
151
|
|
130
|
-
|
131
|
-
attr_reader :channel, :consumer_tag, :envelope, :properties
|
152
|
+
public
|
132
153
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
154
|
+
class BaseConsumer < DefaultConsumer
|
155
|
+
def handleDelivery(consumer_tag, envelope, properties, body)
|
156
|
+
body = String.from_java_bytes(body)
|
157
|
+
headers = Headers.new(channel, consumer_tag, envelope, properties)
|
158
|
+
deliver(headers, body)
|
138
159
|
end
|
139
160
|
|
140
|
-
def
|
141
|
-
@
|
161
|
+
def handleCancel(consumer_tag)
|
162
|
+
@cancelled = true
|
142
163
|
end
|
143
164
|
|
144
|
-
def
|
145
|
-
@
|
165
|
+
def handleCancelOk(consumer_tag)
|
166
|
+
@cancelled = true
|
167
|
+
end
|
168
|
+
|
169
|
+
def start
|
146
170
|
end
|
147
171
|
|
148
|
-
def
|
149
|
-
|
172
|
+
def deliver(headers, message)
|
173
|
+
raise NotImplementedError, 'To be implemented by a subclass'
|
150
174
|
end
|
151
175
|
|
152
|
-
def
|
153
|
-
|
176
|
+
def cancel
|
177
|
+
channel.basic_cancel(consumer_tag)
|
178
|
+
@cancelling = true
|
154
179
|
end
|
155
180
|
end
|
156
181
|
|
157
|
-
|
158
|
-
|
159
|
-
|
182
|
+
private
|
183
|
+
|
184
|
+
class CallbackConsumer < BaseConsumer
|
185
|
+
def initialize(channel, callback)
|
186
|
+
super(channel)
|
187
|
+
@callback = callback
|
188
|
+
@callback_arity = @callback.arity
|
189
|
+
@cancelled = false
|
190
|
+
@cancelling = false
|
160
191
|
end
|
161
192
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
|
193
|
+
def callback(headers, message)
|
194
|
+
if @callback_arity == 2
|
195
|
+
@callback.call(headers, message)
|
196
|
+
else
|
197
|
+
@callback.call(message)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class AsyncCallbackConsumer < CallbackConsumer
|
203
|
+
def initialize(channel, callback, executor)
|
204
|
+
super(channel, callback)
|
205
|
+
@executor = executor
|
206
|
+
@tasks = []
|
166
207
|
end
|
167
208
|
|
168
|
-
def
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
else raise ArgumentError, 'Consumer callback wants no arguments'
|
209
|
+
def deliver(headers, message)
|
210
|
+
unless @executor.shutdown?
|
211
|
+
@executor.submit do
|
212
|
+
callback(headers, message)
|
213
|
+
end
|
174
214
|
end
|
175
215
|
end
|
176
216
|
end
|
177
217
|
|
178
|
-
class
|
179
|
-
include
|
218
|
+
class BlockingCallbackConsumer < CallbackConsumer
|
219
|
+
include JavaConcurrent
|
220
|
+
|
221
|
+
def initialize(channel, buffer_size, callback)
|
222
|
+
super(channel, callback)
|
223
|
+
if buffer_size
|
224
|
+
@internal_queue = ArrayBlockingQueue.new(buffer_size)
|
225
|
+
else
|
226
|
+
@internal_queue = LinkedBlockingQueue.new
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def start
|
231
|
+
interrupted = false
|
232
|
+
until @cancelled || JavaConcurrent::Thread.current_thread.interrupted?
|
233
|
+
begin
|
234
|
+
pair = @internal_queue.take
|
235
|
+
callback(*pair) if pair
|
236
|
+
rescue InterruptedException => e
|
237
|
+
interrupted = true
|
238
|
+
end
|
239
|
+
end
|
240
|
+
while (pair = @internal_queue.poll)
|
241
|
+
callback(*pair)
|
242
|
+
end
|
243
|
+
if interrupted
|
244
|
+
JavaConcurrent::Thread.current_thread.interrupt
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def deliver(*pair)
|
249
|
+
if @cancelling || @cancelled || JavaConcurrent::Thread.current_thread.interrupted?
|
250
|
+
@internal_queue.offer(pair)
|
251
|
+
else
|
252
|
+
begin
|
253
|
+
@internal_queue.put(pair)
|
254
|
+
rescue InterruptedException => e
|
255
|
+
JavaConcurrent::Thread.current_thread.interrupt
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
180
260
|
|
181
|
-
|
261
|
+
class Headers
|
262
|
+
attr_reader :channel, :consumer_tag, :envelope, :properties
|
182
263
|
|
183
|
-
def initialize(channel,
|
264
|
+
def initialize(channel, consumer_tag, envelope, properties)
|
184
265
|
@channel = channel
|
185
|
-
@
|
186
|
-
@
|
266
|
+
@consumer_tag = consumer_tag
|
267
|
+
@envelope = envelope
|
268
|
+
@properties = properties
|
187
269
|
end
|
188
270
|
|
189
|
-
def
|
190
|
-
@
|
271
|
+
def ack(options={})
|
272
|
+
@channel.basic_ack(delivery_tag, options.fetch(:multiple, false))
|
191
273
|
end
|
192
274
|
|
193
|
-
def
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
275
|
+
def reject(options={})
|
276
|
+
@channel.basic_reject(delivery_tag, options.fetch(:requeue, false))
|
277
|
+
end
|
278
|
+
|
279
|
+
begin :envelope_delegation
|
280
|
+
[
|
281
|
+
:delivery_tag,
|
282
|
+
:routing_key,
|
283
|
+
:redeliver,
|
284
|
+
:exchange
|
285
|
+
].each do |envelope_property|
|
286
|
+
define_method(envelope_property) { @envelope.__send__(envelope_property) }
|
287
|
+
end
|
288
|
+
|
289
|
+
alias_method :redelivered?, :redeliver
|
290
|
+
end
|
291
|
+
|
292
|
+
begin :message_properties_delegation
|
293
|
+
[
|
294
|
+
:content_encoding,
|
295
|
+
:content_type,
|
296
|
+
:content_encoding,
|
297
|
+
:headers,
|
298
|
+
:delivery_mode,
|
299
|
+
:priority,
|
300
|
+
:correlation_id,
|
301
|
+
:reply_to,
|
302
|
+
:expiration,
|
303
|
+
:message_id,
|
304
|
+
:timestamp,
|
305
|
+
:type,
|
306
|
+
:user_id,
|
307
|
+
:app_id,
|
308
|
+
:cluster_id
|
309
|
+
].each do |properties_property|
|
310
|
+
define_method(properties_property) { @properties.__send__(properties_property) }
|
311
|
+
end
|
312
|
+
|
313
|
+
def persistent?
|
314
|
+
persistent == 2
|
204
315
|
end
|
205
316
|
end
|
206
317
|
end
|
data/lib/hot_bunnies/version.rb
CHANGED
data/lib/hot_bunnies.rb
CHANGED
@@ -6,13 +6,11 @@ require 'ext/rabbitmq-client'
|
|
6
6
|
|
7
7
|
|
8
8
|
module HotBunnies
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
import com.rabbitmq.client.AMQP
|
9
|
+
java_import 'com.rabbitmq.client.ConnectionFactory'
|
10
|
+
java_import 'com.rabbitmq.client.Connection'
|
11
|
+
java_import 'com.rabbitmq.client.Channel'
|
12
|
+
java_import 'com.rabbitmq.client.DefaultConsumer'
|
13
|
+
java_import 'com.rabbitmq.client.AMQP'
|
16
14
|
|
17
15
|
def self.connect(options={})
|
18
16
|
cf = ConnectionFactory.new
|
@@ -28,10 +26,21 @@ module HotBunnies
|
|
28
26
|
cf.requested_heartbeat = heartbeat_from(options) if include_heartbeat?(options)
|
29
27
|
cf.connection_timeout = connection_timeout_from(options) if include_connection_timeout?(options)
|
30
28
|
|
29
|
+
tls = (options[:ssl] || options[:tls])
|
30
|
+
case tls
|
31
|
+
when true then
|
32
|
+
cf.use_ssl_protocol
|
33
|
+
when String then
|
34
|
+
if options[:trust_manager]
|
35
|
+
cf.use_ssl_protocol(tls, options[:trust_manager])
|
36
|
+
else
|
37
|
+
cf.use_ssl_protocol(tls)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
31
41
|
cf.new_connection
|
32
42
|
end
|
33
43
|
|
34
|
-
|
35
44
|
protected
|
36
45
|
|
37
46
|
def self.hostname_from(options)
|
@@ -1,5 +1,104 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
|
4
|
+
describe 'A consumer of a queue' do
|
5
|
+
let(:connection) { HotBunnies.connect }
|
6
|
+
let(:channel) { connection.create_channel }
|
7
|
+
|
8
|
+
after :each do
|
9
|
+
channel.close
|
10
|
+
connection.close
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'receives messages until cancelled' do
|
14
|
+
exchange = connection.create_channel.default_exchange
|
15
|
+
queue = connection.create_channel.queue("", :auto_delete => true)
|
16
|
+
subscription = queue.subscribe
|
17
|
+
|
18
|
+
messages = []
|
19
|
+
consumer_exited = false
|
20
|
+
|
21
|
+
consumer_thread = Thread.new do
|
22
|
+
subscription.each do |headers, message|
|
23
|
+
messages << message
|
24
|
+
sleep 0.1
|
25
|
+
end
|
26
|
+
consumer_exited = true
|
27
|
+
end
|
28
|
+
|
29
|
+
publisher_thread = Thread.new do
|
30
|
+
20.times do
|
31
|
+
exchange.publish('hello world', :routing_key => queue.name)
|
32
|
+
sleep 0.01
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
sleep 0.2
|
37
|
+
|
38
|
+
subscription.cancel
|
39
|
+
|
40
|
+
consumer_thread.join
|
41
|
+
publisher_thread.join
|
42
|
+
|
43
|
+
messages.should_not be_empty
|
44
|
+
consumer_exited.should be_true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Multiple non-exclusive consumers per queue" do
|
49
|
+
let(:connection) { HotBunnies.connect }
|
50
|
+
let(:channel) { connection.create_channel }
|
51
|
+
|
52
|
+
after :each do
|
53
|
+
channel.close
|
54
|
+
connection.close
|
55
|
+
end
|
56
|
+
|
57
|
+
context "on the same channel (so prefetch levels won't affect message distribution)" do
|
58
|
+
it "have messages distributed to them in the round robin manner" do
|
59
|
+
n = 100
|
60
|
+
mailbox1 = []
|
61
|
+
mailbox2 = []
|
62
|
+
mailbox3 = []
|
63
|
+
|
64
|
+
all_received = java.util.concurrent.CountDownLatch.new(n)
|
65
|
+
consumer_channel = connection.create_channel
|
66
|
+
|
67
|
+
queue = channel.queue("", :auto_delete => true)
|
68
|
+
|
69
|
+
consumer1 = queue.subscribe(:blocking => false) do |metadata, payload|
|
70
|
+
mailbox1 << payload
|
71
|
+
all_received.count_down
|
72
|
+
end
|
73
|
+
consumer2 = queue.subscribe(:blocking => false) do |metadata, payload|
|
74
|
+
mailbox2 << payload
|
75
|
+
all_received.count_down
|
76
|
+
end
|
77
|
+
consumer3 = queue.subscribe(:blocking => false) do |metadata, payload|
|
78
|
+
mailbox3 << payload
|
79
|
+
all_received.count_down
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
sleep 2.0 # let consumers in other threads start.
|
84
|
+
n.times do |i|
|
85
|
+
channel.default_exchange.publish("Message #{i}", :routing_key => queue.name)
|
86
|
+
end
|
87
|
+
|
88
|
+
all_received.await
|
89
|
+
|
90
|
+
mailbox1.size.should >= 33
|
91
|
+
mailbox2.size.should >= 33
|
92
|
+
mailbox3.size.should >= 33
|
93
|
+
|
94
|
+
consumer1.shutdown!
|
95
|
+
consumer2.shutdown!
|
96
|
+
consumer3.shutdown!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
3
102
|
describe "Queue consumer" do
|
4
103
|
let(:connection) { HotBunnies.connect }
|
5
104
|
let(:channel) { connection.create_channel }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
|
4
|
-
describe "HotBunnies" do
|
4
|
+
describe "HotBunnies.connect" do
|
5
5
|
|
6
6
|
#
|
7
7
|
# Examples
|
@@ -16,4 +16,11 @@ describe "HotBunnies" do
|
|
16
16
|
c1 = HotBunnies.connect(:connection_timeout => 3)
|
17
17
|
c1.close
|
18
18
|
end
|
19
|
+
|
20
|
+
if !ENV["CI"] && ENV["TLS_TESTS"]
|
21
|
+
it "supports TLS w/o custom protocol or trust manager" do
|
22
|
+
c1 = HotBunnies.connect(:tls => true, :port => 5671)
|
23
|
+
c1.close
|
24
|
+
end
|
25
|
+
end
|
19
26
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "An AMQP consumer that catches exceptions" do
|
4
|
+
let(:connection) { HotBunnies.connect }
|
5
|
+
let(:channel) { connection.create_channel }
|
6
|
+
|
7
|
+
after :each do
|
8
|
+
channel.close
|
9
|
+
connection.close
|
10
|
+
end
|
11
|
+
|
12
|
+
it "stays up" do
|
13
|
+
mailbox = []
|
14
|
+
exchange = channel.exchange("hot_bunnies.exchanges.fanout#{Time.now.to_i}", :type => :fanout, :auto_delete => true)
|
15
|
+
queue = channel.queue("", :auto_delete => true)
|
16
|
+
|
17
|
+
queue.bind(exchange)
|
18
|
+
consumer = queue.subscribe(:blocking => false) do |meta, payload|
|
19
|
+
n = meta.properties.headers['X-Number']
|
20
|
+
|
21
|
+
begin
|
22
|
+
if n.odd?
|
23
|
+
raise "A failure"
|
24
|
+
else
|
25
|
+
mailbox << payload
|
26
|
+
end
|
27
|
+
rescue Exception => e
|
28
|
+
# no-op
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
25.times do |i|
|
33
|
+
exchange.publish("Message ##{i}", :routing_key => "xyz", :properties => {
|
34
|
+
:headers => {
|
35
|
+
'X-Number' => i
|
36
|
+
}
|
37
|
+
})
|
38
|
+
end
|
39
|
+
|
40
|
+
sleep(0.5)
|
41
|
+
|
42
|
+
mc, cc = queue.status
|
43
|
+
mc.should == 0
|
44
|
+
|
45
|
+
mailbox.size.should == 13
|
46
|
+
consumer.shutdown!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
describe "An AMQP consumer that DOES NOT catch exceptions" do
|
54
|
+
let(:connection) { HotBunnies.connect }
|
55
|
+
let(:channel) { connection.create_channel }
|
56
|
+
|
57
|
+
after :each do
|
58
|
+
channel.close
|
59
|
+
connection.close
|
60
|
+
end
|
61
|
+
|
62
|
+
it "becomes inactive when the channels prefetch is filled with unacked messages" do
|
63
|
+
mailbox = []
|
64
|
+
exchange = channel.exchange("hot_bunnies.exchanges.fanout#{Time.now.to_i}#{rand}", :type => :fanout, :auto_delete => true)
|
65
|
+
queue = channel.queue("", :auto_delete => true)
|
66
|
+
|
67
|
+
channel.prefetch = 5
|
68
|
+
|
69
|
+
queue.bind(exchange)
|
70
|
+
consumer = queue.subscribe(:blocking => false, :ack => true) do |meta, payload|
|
71
|
+
n = meta.properties.headers['X-Number']
|
72
|
+
|
73
|
+
if n.odd?
|
74
|
+
raise "A failure"
|
75
|
+
else
|
76
|
+
mailbox << payload
|
77
|
+
meta.ack
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
25.times do |i|
|
82
|
+
exchange.publish("Message ##{i}", :routing_key => "xyz", :properties => {
|
83
|
+
:headers => {
|
84
|
+
'X-Number' => i
|
85
|
+
}
|
86
|
+
})
|
87
|
+
end
|
88
|
+
|
89
|
+
sleep(0.5)
|
90
|
+
|
91
|
+
message_count, _ = queue.status
|
92
|
+
message_count.should == 15
|
93
|
+
|
94
|
+
mailbox.size.should == 5
|
95
|
+
consumer.shutdown!
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "An AMQP consumer" do
|
4
|
+
let(:connection) { HotBunnies.connect }
|
5
|
+
let(:channel) { connection.create_channel }
|
6
|
+
|
7
|
+
after :each do
|
8
|
+
channel.close
|
9
|
+
connection.close
|
10
|
+
end
|
11
|
+
|
12
|
+
it "can access message metadata (both message properties and delivery information)" do
|
13
|
+
latch = java.util.concurrent.CountDownLatch.new(1)
|
14
|
+
queue = channel.queue("", :auto_delete => true)
|
15
|
+
exchange = channel.exchange("amq.fanout", :type => :fanout)
|
16
|
+
|
17
|
+
queue.bind(exchange, :routing_key => "hotbunnies.key")
|
18
|
+
|
19
|
+
@now = Time.now
|
20
|
+
@payload = "Hello, world!"
|
21
|
+
@meta = nil
|
22
|
+
|
23
|
+
consumer = queue.subscribe(:blocking => false) do |metadata, payload|
|
24
|
+
begin
|
25
|
+
# we will run assertions on the main thread because RSpec uses exceptions
|
26
|
+
# for its purposes every once in a while. MK.
|
27
|
+
@meta = metadata
|
28
|
+
rescue Exception => e
|
29
|
+
e.print_stack_trace
|
30
|
+
ensure
|
31
|
+
latch.count_down
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
exchange.publish(@payload,
|
36
|
+
:properties => { :app_id => "hotbunnies.tests",
|
37
|
+
:priority => 8,
|
38
|
+
:type => "kinda.checkin",
|
39
|
+
# headers table keys can be anything
|
40
|
+
:headers => {
|
41
|
+
"coordinates" => {
|
42
|
+
"latitude" => 59.35,
|
43
|
+
"longitude" => 18.066667
|
44
|
+
},
|
45
|
+
"time" => @now,
|
46
|
+
"participants" => 11,
|
47
|
+
"venue" => "Stockholm",
|
48
|
+
"true_field" => true,
|
49
|
+
"false_field" => false,
|
50
|
+
"nil_field" => nil,
|
51
|
+
"ary_field" => ["one", 2.0, 3, [{ "abc" => 123 }]]
|
52
|
+
},
|
53
|
+
:timestamp => @now,
|
54
|
+
:reply_to => "a.sender",
|
55
|
+
:correlation_id => "r-1",
|
56
|
+
:message_id => "m-1",
|
57
|
+
:content_type => "application/octet-stream",
|
58
|
+
# just an example. MK.
|
59
|
+
:content_encoding => "zip/zap"
|
60
|
+
},
|
61
|
+
:routing_key => "hotbunnies.key")
|
62
|
+
latch.await
|
63
|
+
|
64
|
+
@meta.routing_key.should == "hotbunnies.key"
|
65
|
+
@meta.content_type.should == "application/octet-stream"
|
66
|
+
@meta.content_encoding.should == "zip/zap"
|
67
|
+
@meta.priority.should == 8
|
68
|
+
|
69
|
+
time = Time.at(@meta.headers["time"].getTime/1000)
|
70
|
+
time.to_i.should == @now.to_i
|
71
|
+
|
72
|
+
@meta.headers["coordinates"]["latitude"].should == 59.35
|
73
|
+
@meta.headers["participants"].should == 11
|
74
|
+
@meta.headers["true_field"].should == true
|
75
|
+
@meta.headers["false_field"].should == false
|
76
|
+
@meta.headers["nil_field"].should be_nil
|
77
|
+
|
78
|
+
@meta.timestamp.should == Time.at(@now.to_i)
|
79
|
+
@meta.type.should == "kinda.checkin"
|
80
|
+
@meta.consumer_tag.should_not be_nil
|
81
|
+
@meta.consumer_tag.should_not be_empty
|
82
|
+
@meta.delivery_tag.should == 1
|
83
|
+
@meta.reply_to.should == "a.sender"
|
84
|
+
@meta.correlation_id.should == "r-1"
|
85
|
+
@meta.message_id.should == "m-1"
|
86
|
+
@meta.should_not be_redelivered
|
87
|
+
|
88
|
+
@meta.app_id.should == "hotbunnies.tests"
|
89
|
+
@meta.exchange.should == "amq.fanout"
|
90
|
+
|
91
|
+
consumer.shut_down!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: hot_bunnies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.
|
5
|
+
version: 1.4.0
|
6
6
|
platform: java
|
7
7
|
authors:
|
8
8
|
- Theo Hultberg
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-11-14 00:00:00 Z
|
15
15
|
dependencies: []
|
16
16
|
|
17
17
|
description: A object oriented interface to RabbitMQ that uses the Java driver under the hood
|
@@ -27,6 +27,7 @@ files:
|
|
27
27
|
- .gitignore
|
28
28
|
- .rvmrc
|
29
29
|
- .travis.yml
|
30
|
+
- ChangeLog.md
|
30
31
|
- Gemfile
|
31
32
|
- LICENSE
|
32
33
|
- README.md
|
@@ -45,8 +46,10 @@ files:
|
|
45
46
|
- spec/integration/alternate_exchanges_spec.rb
|
46
47
|
- spec/integration/basic_consume_spec.rb
|
47
48
|
- spec/integration/connection_spec.rb
|
49
|
+
- spec/integration/error_handling_by_consumers_spec.rb
|
48
50
|
- spec/integration/exchange_bind_spec.rb
|
49
51
|
- spec/integration/exchange_declare_spec.rb
|
52
|
+
- spec/integration/message_metadata_access_spec.rb
|
50
53
|
- spec/integration/publisher_confirmations_spec.rb
|
51
54
|
- spec/integration/queue_bind_spec.rb
|
52
55
|
- spec/integration/queue_declare_spec.rb
|