bunny 0.5.1 → 0.5.2

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.
@@ -28,6 +28,9 @@ Sets up a Bunny::Client object ready for connection to a broker/server. _Client_
28
28
  * <tt>:logging => true or false (_default_)</tt> - If set to _true_, session information is sent
29
29
  to STDOUT if <tt>:logfile</tt> has not been specified. Otherwise, session information is written to
30
30
  <tt>:logfile</tt>.
31
+ * <tt>:frame_max => maximum frame size in bytes (default = 131072)</tt>
32
+ * <tt>:channel_max => maximum number of channels (default = 0 no maximum)</tt>
33
+ * <tt>:heartbeat => number of seconds (default = 0 no heartbeat)</tt>
31
34
 
32
35
  =end
33
36
 
@@ -38,15 +41,21 @@ Sets up a Bunny::Client object ready for connection to a broker/server. _Client_
38
41
  @user = opts[:user] || 'guest'
39
42
  @pass = opts[:pass] || 'guest'
40
43
  @vhost = opts[:vhost] || '/'
41
- @frame_max = opts[:frame_max] || 131072
42
- @channel_max = opts[:channel_max] || 5
43
44
  @logfile = opts[:logfile] || nil
44
45
  @logging = opts[:logging] || false
45
- @status = :not_connected
46
+ @status = :not_connected
47
+ @frame_max = opts[:frame_max] || 131072
48
+ @channel_max = opts[:channel_max] || 5
49
+ @heartbeat = opts[:heartbeat] || 0
46
50
  @logger = nil
47
51
  create_logger if @logging
52
+ @channels = []
48
53
  # Create channel 0
49
54
  @channel = Bunny::Channel09.new(self, true)
55
+ @exchanges = {}
56
+ @queues = {}
57
+ @heartbeat_in = false
58
+ @connecting = false
50
59
  end
51
60
 
52
61
  =begin rdoc
@@ -85,18 +94,6 @@ Exchange
85
94
 
86
95
  === DESCRIPTION:
87
96
 
88
- Returns hash of exchanges declared by Bunny.
89
-
90
- =end
91
-
92
- def exchanges
93
- @exchanges ||= {}
94
- end
95
-
96
- =begin rdoc
97
-
98
- === DESCRIPTION:
99
-
100
97
  Declares a queue to the broker/server. If the queue does not exist, a new one is created
101
98
  using the arguments passed in. If the queue already exists, a reference to it is created, provided
102
99
  that the arguments passed in do not conflict with the existing attributes of the queue. If an error
@@ -135,19 +132,16 @@ Queue
135
132
 
136
133
  return queues[name] if queues.has_key?(name)
137
134
 
138
- queue = Bunny::Queue09.new(self, name, opts)
135
+ Bunny::Queue09.new(self, name, opts)
139
136
  end
140
-
141
- =begin rdoc
142
-
143
- === DESCRIPTION:
144
-
145
- Returns hash of queues declared by Bunny.
146
-
147
- =end
148
137
 
149
- def queues
150
- @queues ||= {}
138
+ def send_heartbeat
139
+ # Create a new heartbeat frame
140
+ hb = Qrack::Transport09::Heartbeat.new('')
141
+ # Channel 0 must be used
142
+ switch_channel(0) if @channel.number > 0
143
+ # Send the heartbeat to server
144
+ send_frame(hb)
151
145
  end
152
146
 
153
147
  def send_frame(*args)
@@ -161,10 +155,27 @@ Returns hash of queues declared by Bunny.
161
155
  nil
162
156
  end
163
157
 
164
- def next_frame
165
- frame = Qrack::Transport09::Frame.parse(buffer)
166
- @logger.info("received") { frame } if @logging
167
- frame
158
+ def next_frame(opts = {})
159
+ secs = opts[:timeout] || 0
160
+
161
+ begin
162
+ Timeout::timeout(secs) do
163
+ @frame = Qrack::Transport09::Frame.parse(buffer)
164
+ end
165
+ rescue Timeout::Error
166
+ return :timed_out
167
+ end
168
+
169
+ @logger.info("received") { @frame } if @logging
170
+
171
+ raise Bunny::ConnectionError, 'No connection to server' if (@frame.nil? and !connecting?)
172
+
173
+ if @frame.is_a?(Qrack::Transport09::Heartbeat)
174
+ @heartbeat_in = true
175
+ next_frame
176
+ end
177
+
178
+ @frame
168
179
  end
169
180
 
170
181
  def next_payload
@@ -173,6 +184,43 @@ Returns hash of queues declared by Bunny.
173
184
  end
174
185
 
175
186
  alias next_method next_payload
187
+
188
+ =begin rdoc
189
+
190
+ === DESCRIPTION:
191
+
192
+ Checks to see whether or not an undeliverable message has been returned as a result of a publish
193
+ with the <tt>:immediate</tt> or <tt>:mandatory</tt> options.
194
+
195
+ ==== OPTIONS:
196
+
197
+ * <tt>:timeout => number of seconds (default = 0.1) - The method will wait for a return
198
+ message until this timeout interval is reached.
199
+
200
+ ==== RETURNS:
201
+
202
+ <tt>:no_return</tt> if message was not returned before timeout .
203
+ <tt>{:header, :return_details, :payload}</tt> if message is returned. <tt>:return_details</tt> is
204
+ a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
205
+
206
+ =end
207
+
208
+ def returned_message(opts = {})
209
+ secs = opts[:timeout] || 0.1
210
+ frame = next_frame(:timeout => secs)
211
+
212
+ if frame.is_a?(Symbol)
213
+ return :no_return if frame == :timed_out
214
+ end
215
+
216
+ method = frame.payload
217
+ header = next_payload
218
+ msg = next_payload
219
+ raise Bunny::MessageError, 'unexpected length' if msg.length < header.size
220
+
221
+ # Return the message and related info
222
+ {:header => header, :payload => msg, :return_details => method.arguments}
223
+ end
176
224
 
177
225
  =begin rdoc
178
226
 
@@ -189,12 +237,9 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
189
237
 
190
238
  def close
191
239
  # Close all active channels
192
- channels.each_value do |c|
240
+ channels.each do |c|
193
241
  c.close if c.open?
194
242
  end
195
-
196
- # Set client channel to zero
197
- self.channel = channels[0]
198
243
 
199
244
  # Close connection to AMQP server
200
245
  close_connection
@@ -227,6 +272,8 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
227
272
  =end
228
273
 
229
274
  def start_session
275
+ @connecting = true
276
+
230
277
  # Create/get socket
231
278
  socket
232
279
 
@@ -236,12 +283,14 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
236
283
  # Open connection
237
284
  open_connection
238
285
 
239
- # Open a channel
240
- self.channel = get_channel
241
- channel.open
286
+ # Open another channel because channel zero is used for specific purposes
287
+ c = create_channel()
288
+ c.open
242
289
 
290
+ @connecting = false
291
+
243
292
  # return status
244
- status
293
+ @status = :connected
245
294
  end
246
295
 
247
296
  alias start start_session
@@ -250,7 +299,7 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
250
299
 
251
300
  === DESCRIPTION:
252
301
 
253
- Asks the broker to redeliver all unacknowledged messages on a specifieid channel. Zero or
302
+ Asks the broker to redeliver all unacknowledged messages on a specified channel. Zero or
254
303
  more messages may be redelivered.
255
304
 
256
305
  ==== Options:
@@ -376,22 +425,35 @@ after a rollback.
376
425
  create_logger if @logging
377
426
  end
378
427
 
379
- def channels
380
- @channels ||= {}
381
- end
382
-
383
- def get_channel
384
- channels.each_value do |c|
428
+ def create_channel
429
+ channels.each do |c|
385
430
  return c if (!c.open? and c.number != 0)
386
431
  end
387
432
  # If no channel to re-use instantiate new one
388
433
  Bunny::Channel09.new(self)
389
434
  end
390
435
 
436
+ def switch_channel(chann)
437
+ if (0...channels.size).include? chann
438
+ @channel = channels[chann]
439
+ chann
440
+ else
441
+ raise RuntimeError, "Invalid channel number - #{chann}"
442
+ end
443
+ end
444
+
445
+ def connecting?
446
+ connecting
447
+ end
448
+
391
449
  def init_connection
392
450
  write(Qrack::Protocol09::HEADER)
393
451
  write([0, Qrack::Protocol09::VERSION_MAJOR, Qrack::Protocol09::VERSION_MINOR, Qrack::Protocol09::REVISION].pack('C4'))
394
- raise Bunny::ProtocolError, 'Connection initiation failed' unless next_method.is_a?(Qrack::Protocol09::Connection::Start)
452
+
453
+ frame = next_frame
454
+ if frame.nil? or !frame.payload.is_a?(Qrack::Protocol09::Connection::Start)
455
+ raise Bunny::ProtocolError, 'Connection initiation failed'
456
+ end
395
457
  end
396
458
 
397
459
  def open_connection
@@ -404,12 +466,14 @@ after a rollback.
404
466
  )
405
467
  )
406
468
 
407
- method = next_method
408
- raise Bunny::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if method.nil?
409
-
469
+ frame = next_frame
470
+ raise Bunny::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if frame.nil?
471
+
472
+ method = frame.payload
473
+
410
474
  if method.is_a?(Qrack::Protocol09::Connection::Tune)
411
475
  send_frame(
412
- Qrack::Protocol09::Connection::TuneOk.new( :channel_max => @channel_max, :frame_max => @frame_max, :heartbeat => 0)
476
+ Qrack::Protocol09::Connection::TuneOk.new( :channel_max => @channel_max, :frame_max => @frame_max, :heartbeat => @heartbeat)
413
477
  )
414
478
  end
415
479
 
@@ -421,6 +485,9 @@ after a rollback.
421
485
  end
422
486
 
423
487
  def close_connection
488
+ # Set client channel to zero
489
+ switch_channel(0)
490
+
424
491
  send_frame(
425
492
  Qrack::Protocol09::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
426
493
  )
@@ -446,8 +513,6 @@ after a rollback.
446
513
  return @socket if @socket and (@status == :connected) and not @socket.closed?
447
514
 
448
515
  begin
449
- @status = :not_connected
450
-
451
516
  # Attempt to connect.
452
517
  @socket = timeout(CONNECT_TIMEOUT) do
453
518
  TCPSocket.new(host, port)
@@ -456,7 +521,6 @@ after a rollback.
456
521
  if Socket.constants.include? 'TCP_NODELAY'
457
522
  @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
458
523
  end
459
- @status = :connected
460
524
  rescue => e
461
525
  @status = :not_connected
462
526
  raise Bunny::ServerDownError, e.message
@@ -214,17 +214,21 @@ processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
214
214
  * <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
215
215
  only this consumer can access the queue.
216
216
  * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
217
+ * <tt>:timeout => number of seconds (default = 0 no timeout) - The subscribe loop will continue to wait for
218
+ messages until terminated (Ctrl-C or kill command) or this timeout interval is reached.
217
219
 
218
220
  ==== RETURNS:
219
221
 
220
222
  If <tt>:header => true</tt> returns hash <tt>{:header, :delivery_details, :payload}</tt> for each message.
221
223
  <tt>:delivery_details</tt> is a hash <tt>{:consumer_tag, :delivery_tag, :redelivered, :exchange, :routing_key}</tt>.
222
224
  If <tt>:header => false</tt> only message payload is returned.
225
+ If <tt>:timeout => > 0</tt> is reached returns :timed_out
223
226
 
224
227
  =end
225
228
 
226
229
  def subscribe(opts = {}, &blk)
227
230
  consumer_tag = opts[:consumer_tag] || name
231
+ secs = opts[:timeout] || 0
228
232
 
229
233
  # ignore the :nowait option if passed, otherwise program will hang waiting for a
230
234
  # response from the server causing an error.
@@ -247,13 +251,19 @@ If <tt>:header => false</tt> only message payload is returned.
247
251
  "Error subscribing to queue #{name}" unless
248
252
  client.next_method.is_a?(Qrack::Protocol::Basic::ConsumeOk)
249
253
 
250
- while true
251
- method = client.next_method
252
-
253
- break if method.is_a?(Qrack::Protocol::Basic::CancelOk)
254
+ loop do
255
+ begin
256
+ Timeout::timeout(secs) do
257
+ @method = client.next_method
258
+ end
259
+ rescue Timeout::Error
260
+ return :timed_out
261
+ end
262
+
263
+ break if @method.is_a?(Qrack::Protocol::Basic::CancelOk)
254
264
 
255
265
  # get delivery tag to use for acknowledge
256
- self.delivery_tag = method.delivery_tag if ack
266
+ self.delivery_tag = @method.delivery_tag if ack
257
267
 
258
268
  header = client.next_payload
259
269
  msg = client.next_payload
@@ -287,6 +297,10 @@ the server will not send any more messages for that consumer.
287
297
  opts.delete(:nowait)
288
298
 
289
299
  client.send_frame( Qrack::Protocol::Basic::Cancel.new({ :consumer_tag => consumer_tag }.merge(opts)))
300
+
301
+ raise Bunny::ProtocolError,
302
+ "Error unsubscribing from queue #{name}" unless
303
+ client.next_method.is_a?(Qrack::Protocol::Basic::CancelOk)
290
304
 
291
305
  end
292
306
 
@@ -216,17 +216,21 @@ processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
216
216
  * <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
217
217
  only this consumer can access the queue.
218
218
  * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
219
+ * <tt>:timeout => number of seconds (default = 0 no timeout) - The subscribe loop will continue to wait for
220
+ messages until terminated (Ctrl-C or kill command) or this timeout interval is reached.
219
221
 
220
222
  ==== RETURNS:
221
223
 
222
224
  If <tt>:header => true</tt> returns hash <tt>{:header, :delivery_details, :payload}</tt> for each message.
223
225
  <tt>:delivery_details</tt> is a hash <tt>{:consumer_tag, :delivery_tag, :redelivered, :exchange, :routing_key}</tt>.
224
226
  If <tt>:header => false</tt> only message payload is returned.
227
+ If <tt>:timeout => > 0</tt> is reached returns :timed_out
225
228
 
226
229
  =end
227
230
 
228
231
  def subscribe(opts = {}, &blk)
229
232
  consumer_tag = opts[:consumer_tag] || name
233
+ secs = opts[:timeout] || 0
230
234
 
231
235
  # ignore the :nowait option if passed, otherwise program will hang waiting for a
232
236
  # response from the server causing an error.
@@ -250,13 +254,19 @@ If <tt>:header => false</tt> only message payload is returned.
250
254
  "Error subscribing to queue #{name}" unless
251
255
  client.next_method.is_a?(Qrack::Protocol09::Basic::ConsumeOk)
252
256
 
253
- while true
254
- method = client.next_method
255
-
256
- break if method.is_a?(Qrack::Protocol09::Basic::CancelOk)
257
+ loop do
258
+ begin
259
+ Timeout::timeout(secs) do
260
+ @method = client.next_method
261
+ end
262
+ rescue Timeout::Error
263
+ return :timed_out
264
+ end
265
+
266
+ break if @method.is_a?(Qrack::Protocol09::Basic::CancelOk)
257
267
 
258
268
  # get delivery tag to use for acknowledge
259
- self.delivery_tag = method.delivery_tag if ack
269
+ self.delivery_tag = @method.delivery_tag if ack
260
270
 
261
271
  header = client.next_payload
262
272
  msg = client.next_payload
@@ -290,6 +300,10 @@ the server will not send any more messages for that consumer.
290
300
  opts.delete(:nowait)
291
301
 
292
302
  client.send_frame( Qrack::Protocol09::Basic::Cancel.new({ :consumer_tag => consumer_tag }.merge(opts)))
303
+
304
+ raise Bunny::ProtocolError,
305
+ "Error unsubscribing from queue #{name}" unless
306
+ client.next_method.is_a?(Qrack::Protocol09::Basic::CancelOk)
293
307
 
294
308
  end
295
309
 
@@ -5,8 +5,8 @@ module Qrack
5
5
  CONNECT_TIMEOUT = 1.0
6
6
  RETRY_DELAY = 10.0
7
7
 
8
- attr_reader :status, :host, :vhost, :port, :logging, :spec
9
- attr_accessor :channel, :logfile, :exchanges, :queues, :channels
8
+ attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat
9
+ attr_accessor :channel, :logfile, :exchanges, :queues, :channels, :heartbeat_in, :connecting
10
10
 
11
11
  end
12
12
  end
@@ -19,18 +19,37 @@ describe Bunny do
19
19
  @b.status.should == :connected
20
20
  end
21
21
 
22
+ it "should be able to create and open a new channel" do
23
+ c = @b.create_channel
24
+ c.number.should == 2
25
+ c.should be_an_instance_of(Bunny::Channel)
26
+ @b.channels.size.should == 3
27
+ c.open.should == :open_ok
28
+ @b.channel.number.should == 2
29
+ end
30
+
31
+ it "should be able to switch between channels" do
32
+ @b.channel.number.should == 1
33
+ @b.switch_channel(0)
34
+ @b.channel.number.should == 0
35
+ end
36
+
37
+ it "should raise an error if trying to switch to a non-existent channel" do
38
+ lambda { @b.switch_channel(5)}.should raise_error(RuntimeError)
39
+ end
40
+
22
41
  it "should be able to create an exchange" do
23
42
  exch = @b.exchange('test_exchange')
24
- exch.should be_an_instance_of Bunny::Exchange
43
+ exch.should be_an_instance_of(Bunny::Exchange)
25
44
  exch.name.should == 'test_exchange'
26
- @b.exchanges.has_key?('test_exchange').should be true
45
+ @b.exchanges.has_key?('test_exchange').should be(true)
27
46
  end
28
47
 
29
48
  it "should be able to create a queue" do
30
49
  q = @b.queue('test1')
31
- q.should be_an_instance_of Bunny::Queue
50
+ q.should be_an_instance_of(Bunny::Queue)
32
51
  q.name.should == 'test1'
33
- @b.queues.has_key?('test1').should be true
52
+ @b.queues.has_key?('test1').should be(true)
34
53
  end
35
54
 
36
55
  it "should be able to set QoS" do