bunny 0.9.0.pre13 → 0.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +23 -0
  3. data/README.md +2 -2
  4. data/lib/bunny.rb +15 -2
  5. data/lib/bunny/authentication/credentials_encoder.rb +18 -0
  6. data/lib/bunny/authentication/external_mechanism_encoder.rb +1 -0
  7. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  8. data/lib/bunny/channel.rb +9 -3
  9. data/lib/bunny/channel_id_allocator.rb +8 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +3 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +3 -0
  13. data/lib/bunny/consumer.rb +2 -0
  14. data/lib/bunny/consumer_tag_generator.rb +1 -0
  15. data/lib/bunny/consumer_work_pool.rb +2 -0
  16. data/lib/bunny/delivery_info.rb +18 -2
  17. data/lib/bunny/exceptions.rb +49 -10
  18. data/lib/bunny/framing.rb +3 -0
  19. data/lib/bunny/heartbeat_sender.rb +4 -1
  20. data/lib/bunny/message_properties.rb +22 -0
  21. data/lib/bunny/queue.rb +49 -0
  22. data/lib/bunny/reader_loop.rb +1 -0
  23. data/lib/bunny/return_info.rb +11 -0
  24. data/lib/bunny/session.rb +53 -19
  25. data/lib/bunny/socket.rb +1 -0
  26. data/lib/bunny/ssl_socket.rb +29 -5
  27. data/lib/bunny/system_timer.rb +2 -0
  28. data/lib/bunny/test_kit.rb +4 -3
  29. data/lib/bunny/transport.rb +41 -25
  30. data/lib/bunny/version.rb +2 -1
  31. data/spec/compatibility/queue_declare_spec.rb +4 -0
  32. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +33 -0
  33. data/spec/higher_level_api/integration/basic_get_spec.rb +35 -0
  34. data/spec/higher_level_api/integration/basic_nack_spec.rb +19 -1
  35. data/spec/higher_level_api/integration/connection_spec.rb +5 -0
  36. data/spec/higher_level_api/integration/queue_unbind_spec.rb +23 -2
  37. data/spec/higher_level_api/integration/tls_connection_spec.rb +47 -0
  38. data/spec/lower_level_api/integration/basic_cancel_spec.rb +8 -1
  39. data/spec/tls/cacert.pem +18 -0
  40. data/spec/tls/client_cert.pem +18 -0
  41. data/spec/tls/client_key.pem +27 -0
  42. data/spec/tls/server_cert.pem +18 -0
  43. data/spec/tls/server_key.pem +27 -0
  44. metadata +16 -2
@@ -1,10 +1,12 @@
1
1
  module Bunny
2
+ # @private
2
3
  module Framing
3
4
  ENCODINGS_SUPPORTED = defined? Encoding
4
5
  HEADER_SLICE = (0..6).freeze
5
6
  DATA_SLICE = (7..-1).freeze
6
7
  PAYLOAD_SLICE = (0..-2).freeze
7
8
 
9
+ # @private
8
10
  module String
9
11
  class Frame < AMQ::Protocol::Frame
10
12
  def self.decode(string)
@@ -30,6 +32,7 @@ module Bunny
30
32
  end # String
31
33
 
32
34
 
35
+ # @private
33
36
  module IO
34
37
  class Frame < AMQ::Protocol::Frame
35
38
  def self.decode(io)
@@ -3,6 +3,9 @@ require "amq/protocol/client"
3
3
  require "amq/protocol/frame"
4
4
 
5
5
  module Bunny
6
+ # Periodically sends heartbeats, keeping track of the last publishing activity.
7
+ #
8
+ # @private
6
9
  class HeartbeatSender
7
10
 
8
11
  #
@@ -50,7 +53,7 @@ module Bunny
50
53
  @logger.error "I/O error in the hearbeat sender: #{ioe.message}"
51
54
  stop
52
55
  rescue Exception => e
53
- @logger.error "I/O error in the hearbeat sender: #{ioe.message}"
56
+ @logger.error "Error in the hearbeat sender: #{e.message}"
54
57
  stop
55
58
  end
56
59
  end
@@ -14,82 +14,104 @@ module Bunny
14
14
  # API
15
15
  #
16
16
 
17
+ # @private
17
18
  def initialize(properties)
18
19
  @properties = properties
19
20
  end
20
21
 
22
+ # Iterates over the message properties
23
+ # @see Enumerable#each
21
24
  def each(*args, &block)
22
25
  @properties.each(*args, &block)
23
26
  end
24
27
 
28
+ # Accesses message properties by key
29
+ # @see Hash#[]
25
30
  def [](k)
26
31
  @properties[k]
27
32
  end
28
33
 
34
+ # @return [Hash] Hash representation of this delivery info
29
35
  def to_hash
30
36
  @properties
31
37
  end
32
38
 
39
+ # @private
33
40
  def to_s
34
41
  to_hash.to_s
35
42
  end
36
43
 
44
+ # @private
37
45
  def inspect
38
46
  to_hash.inspect
39
47
  end
40
48
 
49
+ # @return [String] (Optional) content type of the message, as set by the publisher
41
50
  def content_type
42
51
  @properties[:content_type]
43
52
  end
44
53
 
54
+ # @return [String] (Optional) content encoding of the message, as set by the publisher
45
55
  def content_encoding
46
56
  @properties[:content_encoding]
47
57
  end
48
58
 
59
+ # @return [String] Message headers
49
60
  def headers
50
61
  @properties[:headers]
51
62
  end
52
63
 
64
+ # @return [Integer] Delivery mode (persistent or transient)
53
65
  def delivery_mode
54
66
  @properties[:delivery_mode]
55
67
  end
56
68
 
69
+ # @return [Integer] Message priority, as set by the publisher
57
70
  def priority
58
71
  @properties[:priority]
59
72
  end
60
73
 
74
+ # @return [String] What message this message is a reply to (or corresponds to), as set by the publisher
61
75
  def correlation_id
62
76
  @properties[:correlation_id]
63
77
  end
64
78
 
79
+ # @return [String] (Optional) How to reply to the publisher (usually a reply queue name)
65
80
  def reply_to
66
81
  @properties[:reply_to]
67
82
  end
68
83
 
84
+ # @return [String] Message expiration, as set by the publisher
69
85
  def expiration
70
86
  @properties[:expiration]
71
87
  end
72
88
 
89
+ # @return [String] Message ID, as set by the publisher
73
90
  def message_id
74
91
  @properties[:message_id]
75
92
  end
76
93
 
94
+ # @return [Time] Message timestamp, as set by the publisher
77
95
  def timestamp
78
96
  @properties[:timestamp]
79
97
  end
80
98
 
99
+ # @return [String] Message type, as set by the publisher
81
100
  def type
82
101
  @properties[:type]
83
102
  end
84
103
 
104
+ # @return [String] Publishing user, as set by the publisher
85
105
  def user_id
86
106
  @properties[:user_id]
87
107
  end
88
108
 
109
+ # @return [String] Publishing application, as set by the publisher
89
110
  def app_id
90
111
  @properties[:app_id]
91
112
  end
92
113
 
114
+ # @return [String] Cluster ID, as set by the publisher
93
115
  def cluster_id
94
116
  @properties[:cluster_id]
95
117
  end
@@ -256,6 +256,55 @@ module Bunny
256
256
  end
257
257
  end
258
258
 
259
+ # @param [Hash] opts Options
260
+ #
261
+ # @option opts [Boolean] :ack (false) Will the message be acknowledged manually?
262
+ # @option opts [Float] :timeout (1.0) Max amount of time, in seconds, to wait before raising an exception
263
+ #
264
+ # @return [Array] Triple of delivery info, message properties and message content.
265
+ # If the queue is empty, all three will be nils.
266
+ #
267
+ # @raises [Bunny::ClientException] When no message is available in the amount of time provided via the :timeout option
268
+ #
269
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
270
+ # @see Bunny::Queue#subscribe
271
+ # @api public
272
+ #
273
+ # @example
274
+ # conn = Bunny.new
275
+ # conn.start
276
+ #
277
+ # ch = conn.create_channel
278
+ # q = ch.queue("test1")
279
+ # x = ch.default_exchange
280
+ # x.publish("Hello, everybody!", :routing_key => 'test1')
281
+ #
282
+ # delivery_info, properties, payload = q.pop_waiting(:timeout => 0.5)
283
+ #
284
+ # puts "This is the message: " + payload + "\n\n"
285
+ # conn.close
286
+ def pop_waiting(opts = {:ack => false, :timeout => 1.0}, &block)
287
+ delivery_info, properties, content = nil, nil, nil
288
+
289
+ Bunny::Timer.timeout(opts[:timeout], ClientTimeout) do
290
+ loop do
291
+ delivery_info, properties, content = @channel.basic_get(@name, opts)
292
+
293
+ if !content.nil?
294
+ break
295
+ end
296
+ end
297
+ end
298
+
299
+ if block
300
+ block.call(delivery_info, properties, content)
301
+ else
302
+ [delivery_info, properties, content]
303
+ end
304
+ end
305
+ alias get_waiting pop_waiting
306
+
307
+
259
308
  # Publishes a message to the queue via default exchange. Takes the same arguments
260
309
  # as {Bunny::Exchange#publish}
261
310
  #
@@ -6,6 +6,7 @@ module Bunny
6
6
  # This loop uses a separate thread internally.
7
7
  #
8
8
  # This mimics the way RabbitMQ Java is designed quite closely.
9
+ # @private
9
10
  class ReaderLoop
10
11
 
11
12
  def initialize(transport, session, session_thread)
@@ -24,38 +24,49 @@ module Bunny
24
24
  }
25
25
  end
26
26
 
27
+ # Iterates over the returned delivery properties
28
+ # @see Enumerable#each
27
29
  def each(*args, &block)
28
30
  @hash.each(*args, &block)
29
31
  end
30
32
 
33
+ # Accesses returned delivery properties by key
34
+ # @see Hash#[]
31
35
  def [](k)
32
36
  @hash[k]
33
37
  end
34
38
 
39
+ # @return [Hash] Hash representation of this returned delivery info
35
40
  def to_hash
36
41
  @hash
37
42
  end
38
43
 
44
+ # @private
39
45
  def to_s
40
46
  to_hash.to_s
41
47
  end
42
48
 
49
+ # @private
43
50
  def inspect
44
51
  to_hash.inspect
45
52
  end
46
53
 
54
+ # @return [Integer] Reply (status) code of the cause
47
55
  def reply_code
48
56
  @basic_return.reply_code
49
57
  end
50
58
 
59
+ # @return [Integer] Reply (status) text of the cause, explaining why the message was returned
51
60
  def reply_text
52
61
  @basic_return.reply_text
53
62
  end
54
63
 
64
+ # @return [String] Exchange the message was published to
55
65
  def exchange
56
66
  @basic_return.exchange
57
67
  end
58
68
 
69
+ # @return [String] Routing key the message has
59
70
  def routing_key
60
71
  @basic_return.routing_key
61
72
  end
@@ -54,8 +54,10 @@ module Bunny
54
54
  :information => "http://rubybunny.info",
55
55
  }
56
56
 
57
+ # @private
57
58
  DEFAULT_LOCALE = "en_GB"
58
59
 
60
+ # Default reconnection interval for TCP connection failures
59
61
  DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
60
62
 
61
63
 
@@ -63,6 +65,8 @@ module Bunny
63
65
  # API
64
66
  #
65
67
 
68
+ # @return [Bunny::Transport]
69
+ attr_reader :transport
66
70
  attr_reader :status, :host, :port, :heartbeat, :user, :pass, :vhost, :frame_max, :threaded
67
71
  attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
68
72
  attr_reader :default_channel
@@ -138,6 +142,8 @@ module Bunny
138
142
  @channels = Hash.new
139
143
 
140
144
  self.reset_continuations
145
+
146
+ self.initialize_transport
141
147
  end
142
148
 
143
149
  # @return [String] RabbitMQ hostname (or IP address) used
@@ -169,7 +175,20 @@ module Bunny
169
175
  @threaded
170
176
  end
171
177
 
172
- # Starts connection process
178
+ def configure_socket(&block)
179
+ raise ArgumentError, "No block provided!" if block.nil?
180
+
181
+ if @transport
182
+ @transport.configure_socket(&block)
183
+ else
184
+ @socket_configurator = block
185
+ end
186
+ end
187
+
188
+ # Starts the connection process.
189
+ #
190
+ # @see http://rubybunny.info/articles/getting_started.html
191
+ # @see http://rubybunny.info/articles/connecting.html
173
192
  # @api public
174
193
  def start
175
194
  return self if connected?
@@ -183,6 +202,13 @@ module Bunny
183
202
  self.maybe_close_transport
184
203
  self.initialize_transport
185
204
 
205
+ @transport.initialize_socket
206
+ @transport.connect
207
+
208
+ if @socket_configurator
209
+ @transport.configure_socket(&@socket_configurator)
210
+ end
211
+
186
212
  self.init_connection
187
213
  self.open_connection
188
214
 
@@ -198,6 +224,9 @@ module Bunny
198
224
  self
199
225
  end
200
226
 
227
+ # Socket operation timeout used by this connection
228
+ # @return [Integer]
229
+ # @private
201
230
  def read_write_timeout
202
231
  @transport.read_write_timeout
203
232
  end
@@ -397,6 +426,7 @@ module Bunny
397
426
  raise @last_connection_error if @last_connection_error
398
427
  end
399
428
 
429
+ # @private
400
430
  def handle_frameset(ch_number, frames)
401
431
  method = frames.first
402
432
 
@@ -486,11 +516,15 @@ module Bunny
486
516
  when 501 then
487
517
  FrameError
488
518
  when 503 then
489
- InvalidCommand
519
+ CommandInvalid
490
520
  when 504 then
491
521
  ChannelError
492
522
  when 505 then
493
523
  UnexpectedFrame
524
+ when 506 then
525
+ ResourceError
526
+ when 541 then
527
+ InternalError
494
528
  else
495
529
  raise "Unknown reply code: #{frame.reply_code}, text: #{frame.reply_text}"
496
530
  end
@@ -615,7 +649,7 @@ module Bunny
615
649
  # Sends multiple frames, one by one. For thread safety this method takes a channel
616
650
  # object and synchronizes on it.
617
651
  #
618
- # @api private
652
+ # @private
619
653
  def send_frameset(frames, channel)
620
654
  # some developers end up sharing channels between threads and when multiple
621
655
  # threads publish on the same channel aggressively, at some point frames will be
@@ -632,7 +666,7 @@ module Bunny
632
666
  # object and synchronizes on it. Uses transport implementation that does not perform
633
667
  # timeout control.
634
668
  #
635
- # @api private
669
+ # @private
636
670
  def send_frameset_without_timeout(frames, channel)
637
671
  # some developers end up sharing channels between threads and when multiple
638
672
  # threads publish on the same channel aggressively, at some point frames will be
@@ -653,7 +687,7 @@ module Bunny
653
687
 
654
688
  protected
655
689
 
656
- # @api private
690
+ # @private
657
691
  def init_connection
658
692
  self.send_preamble
659
693
 
@@ -668,7 +702,7 @@ module Bunny
668
702
  @status = :connected
669
703
  end
670
704
 
671
- # @api private
705
+ # @private
672
706
  def open_connection
673
707
  @transport.send_frame(AMQ::Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale))
674
708
  @logger.debug "Sent connection.start-ok"
@@ -732,7 +766,7 @@ module Bunny
732
766
  0 == val || val.nil?
733
767
  end
734
768
 
735
- # @api private
769
+ # @private
736
770
  def negotiate_value(client_value, server_value)
737
771
  return server_value if client_value == :server
738
772
 
@@ -743,59 +777,59 @@ module Bunny
743
777
  end
744
778
  end
745
779
 
746
- # @api private
780
+ # @private
747
781
  def initialize_heartbeat_sender
748
782
  @logger.debug "Initializing heartbeat sender..."
749
783
  @heartbeat_sender = HeartbeatSender.new(@transport, @logger)
750
784
  @heartbeat_sender.start(@heartbeat)
751
785
  end
752
786
 
753
- # @api private
787
+ # @private
754
788
  def maybe_shutdown_heartbeat_sender
755
789
  @heartbeat_sender.stop if @heartbeat_sender
756
790
  end
757
791
 
758
- # @api private
792
+ # @private
759
793
  def initialize_transport
760
794
  @transport = Transport.new(self, @host, @port, @opts.merge(:session_thread => Thread.current))
761
795
  end
762
796
 
763
- # @api private
797
+ # @private
764
798
  def maybe_close_transport
765
799
  @transport.close if @transport
766
800
  end
767
801
 
768
802
  # Sends AMQ protocol header (also known as preamble).
769
- # @api private
803
+ # @private
770
804
  def send_preamble
771
805
  @transport.write(AMQ::Protocol::PREAMBLE)
772
806
  @logger.debug "Sent protocol preamble"
773
807
  end
774
808
 
775
809
 
776
- # @api private
810
+ # @private
777
811
  def encode_credentials(username, password)
778
812
  @credentials_encoder.encode_credentials(username, password)
779
813
  end # encode_credentials(username, password)
780
814
 
781
- # @api private
815
+ # @private
782
816
  def credentials_encoder_for(mechanism)
783
817
  Authentication::CredentialsEncoder.for_session(self)
784
818
  end
785
819
 
786
820
  if defined?(JRUBY_VERSION)
787
- # @api private
821
+ # @private
788
822
  def reset_continuations
789
823
  @continuations = Concurrent::LinkedContinuationQueue.new
790
824
  end
791
825
  else
792
- # @api private
826
+ # @private
793
827
  def reset_continuations
794
828
  @continuations = Concurrent::ContinuationQueue.new
795
829
  end
796
830
  end
797
831
 
798
- # @api private
832
+ # @private
799
833
  def wait_on_continuations
800
834
  unless @threaded
801
835
  reader_loop.run_once until @continuations.length > 0
@@ -804,7 +838,7 @@ module Bunny
804
838
  @continuations.poll(@continuation_timeout)
805
839
  end
806
840
 
807
- # @api private
841
+ # @private
808
842
  def init_logger(level)
809
843
  @logger = ::Logger.new(@logfile)
810
844
  @logger.level = normalize_log_level(level)
@@ -813,7 +847,7 @@ module Bunny
813
847
  @logger
814
848
  end
815
849
 
816
- # @api private
850
+ # @private
817
851
  def normalize_log_level(level)
818
852
  case level
819
853
  when :debug, Logger::DEBUG, "debug" then Logger::DEBUG