hot_bunnies 1.3.8-java → 1.4.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Continuous Integration status](https://secure.travis-ci.org/ruby-amqp/hot_bunnies.png)](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
|