amqp 0.8.0.rc2 → 0.8.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +2 -3
  2. data/.travis.yml +5 -2
  3. data/.yardopts +2 -0
  4. data/CHANGELOG +17 -20
  5. data/Gemfile +7 -5
  6. data/README.textile +67 -29
  7. data/Rakefile +6 -0
  8. data/amqp.gemspec +5 -5
  9. data/docs/08Migration.textile +27 -0
  10. data/docs/Bindings.textile +27 -0
  11. data/docs/ConnectingToTheBroker.textile +277 -0
  12. data/docs/DocumentationGuidesIndex.textile +25 -0
  13. data/docs/Durability.textile +27 -0
  14. data/docs/ErrorHandling.textile +84 -0
  15. data/docs/Exchanges.textile +27 -0
  16. data/docs/GettingStarted.textile +585 -0
  17. data/docs/Queues.textile +27 -0
  18. data/docs/RabbitMQVersions.textile +12 -2
  19. data/docs/Routing.textile +27 -0
  20. data/docs/TLS.textile +27 -0
  21. data/docs/VendorSpecificExtensions.textile +11 -1
  22. data/examples/{various → channels}/open_channel_without_assignment.rb +0 -4
  23. data/examples/channels/prefetch_as_constructor_argument.rb +31 -0
  24. data/examples/channels/qos_aka_prefetch.rb +34 -0
  25. data/examples/channels/qos_aka_prefetch_without_callback.rb +32 -0
  26. data/examples/error_handling/channel_level_exception.rb +47 -0
  27. data/examples/error_handling/channel_level_exception_with_multiple_channels_involved.rb +54 -0
  28. data/examples/error_handling/connection_loss_handler.rb +39 -0
  29. data/examples/error_handling/global_channel_level_exception_handler.rb +65 -0
  30. data/examples/error_handling/handling_authentication_failure_with_a_callback.rb +33 -0
  31. data/examples/error_handling/tcp_connection_failure_handling_with_a_rescue_block.rb +30 -0
  32. data/examples/error_handling/tcp_connection_failure_with_a_callback.rb +28 -0
  33. data/examples/{various → exchanges}/declare_an_exchange_without_assignment.rb +0 -4
  34. data/examples/guides/getting_started/01_hello_world.rb +24 -0
  35. data/examples/guides/getting_started/02_hello_world_dslified.rb +23 -0
  36. data/examples/guides/getting_started/03_babblr.rb +33 -0
  37. data/examples/guides/getting_started/04_weathr.rb +56 -0
  38. data/examples/hello_world.rb +12 -13
  39. data/examples/hello_world_with_eventmachine_in_a_separate_thread.rb +37 -0
  40. data/examples/{various → legacy}/ack.rb +0 -0
  41. data/examples/{various → legacy}/callbacks.rb +0 -0
  42. data/examples/{various → legacy}/clock.rb +0 -0
  43. data/examples/{various → legacy}/hashtable.rb +0 -0
  44. data/examples/{various → legacy}/logger.rb +0 -0
  45. data/examples/{various → legacy}/multiclock.rb +0 -0
  46. data/examples/{various → legacy}/pingpong.rb +0 -2
  47. data/examples/{various → legacy}/primes-simple.rb +0 -0
  48. data/examples/{various → legacy}/primes.rb +0 -2
  49. data/examples/{various → legacy}/stocks.rb +0 -0
  50. data/examples/{various → queues}/automatic_binding_for_default_direct_exchange.rb +4 -0
  51. data/examples/{various → queues}/basic_get.rb +0 -2
  52. data/examples/{various → queues}/declare_a_queue_without_assignment.rb +0 -4
  53. data/examples/queues/declare_and_bind_a_server_named_queue.rb +43 -0
  54. data/examples/{various → queues}/queue_status.rb +3 -8
  55. data/examples/{various → routing}/pubsub.rb +0 -0
  56. data/examples/{various → routing}/weather_updates.rb +1 -1
  57. data/lib/amqp/channel.rb +231 -52
  58. data/lib/amqp/client.rb +6 -3
  59. data/lib/amqp/connection.rb +9 -10
  60. data/lib/amqp/deprecated/fork.rb +3 -3
  61. data/lib/amqp/deprecated/logger.rb +1 -0
  62. data/lib/amqp/deprecated/mq.rb +23 -1
  63. data/lib/amqp/deprecated/rpc.rb +1 -0
  64. data/lib/amqp/exceptions.rb +45 -3
  65. data/lib/amqp/exchange.rb +29 -35
  66. data/lib/amqp/ext/em.rb +0 -7
  67. data/lib/amqp/ext/emfork.rb +3 -2
  68. data/lib/amqp/header.rb +4 -0
  69. data/lib/amqp/queue.rb +96 -33
  70. data/lib/amqp/session.rb +140 -0
  71. data/lib/amqp/version.rb +6 -1
  72. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +7 -7
  73. data/spec/integration/channel_level_exception_with_multiple_channels_spec.rb +69 -0
  74. data/spec/integration/declare_and_immediately_bind_a_server_named_queue_spec.rb +42 -0
  75. data/spec/integration/queue_declaration_spec.rb +8 -24
  76. data/spec/integration/queue_redeclaration_with_incompatible_attributes_spec.rb +43 -0
  77. data/spec/unit/amqp/connection_spec.rb +1 -1
  78. metadata +200 -182
  79. data/lib/amqp/basic_client.rb +0 -27
File without changes
@@ -11,7 +11,7 @@ require "amqp"
11
11
  EventMachine.run do
12
12
  AMQP.connect do |connection|
13
13
  channel = AMQP::Channel.new(connection)
14
- exchange = channel.topic("pub/sub")
14
+ exchange = channel.topic("pub/sub", :auto_delete => true)
15
15
 
16
16
  # Subscribers.
17
17
  channel.queue("", :exclusive => true) do |queue|
@@ -4,7 +4,9 @@ require "amqp/exchange"
4
4
  require "amqp/queue"
5
5
 
6
6
  module AMQP
7
- # To quote {AMQP 0.9.1 specification http://bit.ly/hw2ELX}:
7
+ # h2. What are AMQP channels
8
+ #
9
+ # To quote {http://bit.ly/hw2ELX AMQP 0.9.1 specification}:
8
10
  #
9
11
  # AMQP is a multi-channelled protocol. Channels provide a way to multiplex
10
12
  # a heavyweight TCP/IP connection into several light weight connections.
@@ -13,11 +15,41 @@ module AMQP
13
15
  # Channels are independent of each other and can perform different functions simultaneously
14
16
  # with other channels, the available bandwidth being shared between the concurrent activities.
15
17
  #
18
+ # h2. Opening a channel
16
19
  #
17
- # h2. RabbitMQ extensions.
20
+ # *Channels are opened asynchronously*. There are two ways to do it: using a callback or pseudo-synchronous mode.
18
21
  #
19
- # AMQP gem supports several RabbitMQ extensions taht extend Channel functionality.
20
- # Learn more in {file:docs/VendorSpecificExtensions.textile}
22
+ # @example Opening a channel with a callback
23
+ # # this assumes EventMachine reactor is running
24
+ # AMQP.connect("amqp://guest:guest@dev.rabbitmq.com:5672") do |client|
25
+ # AMQP::Channel.new(client) do |channel, open_ok|
26
+ # # when this block is executed, channel is open and ready for use
27
+ # end
28
+ # end
29
+ #
30
+ # <script src="https://gist.github.com/939480.js?file=gistfile1.rb"></script>
31
+ #
32
+ # Unless your application needs multiple channels, this approach is recommended. Alternatively,
33
+ # AMQP::Channel can be instantiated without a block. Then returned channel is not immediately open,
34
+ # however, it can be used as if it was a synchronous, blocking method:
35
+ #
36
+ # @example Instantiating a channel that will be open eventually
37
+ # # this assumes EventMachine reactor is running
38
+ # AMQP.connect("amqp://guest:guest@dev.rabbitmq.com:5672") do |client|
39
+ # channel = AMQP::Channel.new(client)
40
+ # exchange = channel.default_exchange
41
+ #
42
+ # # ...
43
+ # end
44
+ #
45
+ # <script src="https://gist.github.com/939482.js?file=gistfile1.rb"></script>
46
+ #
47
+ # Even though in the example above channel isn't immediately open, it is safe to declare exchanges using
48
+ # it. Exchange declaration will be delayed until after channel is open. Same applies to queue declaration
49
+ # and other operations on exchanges and queues. Library methods that rely on channel being open will be
50
+ # enqueued and executed in a FIFO manner when broker confirms channel opening.
51
+ # Note, however, that *this "pseudo-synchronous mode" is easy to abuse and introduce race conditions AMQP gem
52
+ # cannot resolve for you*. AMQP is an inherently asynchronous protocol and AMQP gem embraces this fact.
21
53
  #
22
54
  #
23
55
  # h2. Key methods
@@ -31,6 +63,8 @@ module AMQP
31
63
  # * {Channel#topic}
32
64
  # * {Channel#close}
33
65
  #
66
+ # refer to documentation for those methods for usage examples.
67
+ #
34
68
  # Channel provides a number of convenience methods that instantiate queues and exchanges
35
69
  # of various types associated with this channel:
36
70
  #
@@ -40,9 +74,71 @@ module AMQP
40
74
  # * {Channel#fanout}
41
75
  # * {Channel#topic}
42
76
  #
77
+ #
78
+ # h2. Error handling
79
+ #
80
+ # It is possible (and, indeed, recommended) to handle channel-level exceptions by defining an errback using #on_error:
81
+ #
82
+ # @example Queue declaration with incompatible attributes results in a channel-level exception
83
+ # AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
84
+ # AMQP::Channel.new do |channel, open_ok|
85
+ # puts "Channel ##{channel.id} is now open!"
86
+ #
87
+ # channel.on_error do |ch, close|
88
+ # puts "Handling channel-level exception"
89
+ #
90
+ # connection.close {
91
+ # EM.stop { exit }
92
+ # }
93
+ # end
94
+ #
95
+ # EventMachine.add_timer(0.4) do
96
+ # # these two definitions result in a race condition. For sake of this example,
97
+ # # however, it does not matter. Whatever definition succeeds first, 2nd one will
98
+ # # cause a channel-level exception (because attributes are not identical)
99
+ # AMQP::Queue.new(channel, "amqpgem.examples.channel_exception", :auto_delete => true, :durable => false) do |queue|
100
+ # puts "#{queue.name} is ready to go"
101
+ # end
102
+ #
103
+ # AMQP::Queue.new(channel, "amqpgem.examples.channel_exception", :auto_delete => true, :durable => true) do |queue|
104
+ # puts "#{queue.name} is ready to go"
105
+ # end
106
+ # end
107
+ # end
108
+ # end
109
+ #
110
+ # <script src="https://gist.github.com/939490.js?file=gistfile1.rb"></script>
111
+ #
112
+ # When channel-level exception is indicated by the broker and errback defined using #on_error is run, channel is already
113
+ # closed and all queue and exchange objects associated with this channel are reset. The recommended way to recover from
114
+ # channel-level exceptions is to open a new channel and re-instantiate queues, exchanges and bindings your application
115
+ # needs.
116
+ #
117
+ #
118
+ #
119
+ # h2. Closing a channel
120
+ #
43
121
  # Channels are opened when objects is instantiated and closed using {#close} method when application no longer
44
122
  # needs it.
45
123
  #
124
+ # @example Closing a channel your application no longer needs
125
+ # # this assumes EventMachine reactor is running
126
+ # AMQP.connect("amqp://guest:guest@dev.rabbitmq.com:5672") do |client|
127
+ # AMQP::Channel.new(client) do |channel, open_ok|
128
+ # channel.close do |close_ok|
129
+ # # when this block is executed, channel is successfully closed
130
+ # end
131
+ # end
132
+ # end
133
+ #
134
+ # <script src="https://gist.github.com/939483.js?file=gistfile1.rb"></script>
135
+ #
136
+ #
137
+ # h2. RabbitMQ extensions.
138
+ #
139
+ # AMQP gem supports several RabbitMQ extensions taht extend Channel functionality.
140
+ # Learn more in {file:docs/VendorSpecificExtensions.textile}
141
+ #
46
142
  # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.2.5)
47
143
  class Channel < AMQ::Client::Channel
48
144
 
@@ -63,16 +159,18 @@ module AMQP
63
159
  # @note We encourage you to not rely on default AMQP connection and pass connection parameter
64
160
  # explicitly.
65
161
  #
66
- # @param [AMQ::Client::EventMachineAdapter] Connection to open this channel on. If not given, default AMQP
67
- # connection (accessible via {AMQP.connection}) will be used.
68
- # @param [Integer] Channel id. Must not be greater than max channel id client and broker
69
- # negotiated on during connection setup. Almost always the right thing to do
70
- # is to let AMQP gem pick channel identifier for you.
162
+ # @param [AMQP::Session] connection Connection to open this channel on. If not given, default AMQP
163
+ # connection (accessible via {AMQP.connection}) will be used.
164
+ # @param [Integer] id Channel id. Must not be greater than max channel id client and broker
165
+ # negotiated on during connection setup. Almost always the right thing to do
166
+ # is to let AMQP gem pick channel identifier for you. If you want to get next
167
+ # channel id, use {AMQP::Channel.next_channel_id} (it is thread-safe).
168
+ # @param [Hash] options A hash of options
71
169
  #
72
170
  # @example Instantiating a channel for default connection (accessible as AMQP.connection)
73
171
  #
74
172
  # AMQP.connect do |connection|
75
- # AMQP::Channel.new(connection) do |channel|
173
+ # AMQP::Channel.new(connection) do |channel, open_ok|
76
174
  # # channel is ready: set up your messaging flow by creating exchanges,
77
175
  # # queues, binding them together and so on.
78
176
  # end
@@ -81,23 +179,33 @@ module AMQP
81
179
  # @example Instantiating a channel for explicitly given connection
82
180
  #
83
181
  # AMQP.connect do |connection|
84
- # AMQP::Channel.new(connection) do |channel|
85
- # # channel is ready: set up your messaging flow by creating exchanges,
86
- # # queues, binding them together and so on.
182
+ # AMQP::Channel.new(connection) do |channel, open_ok|
183
+ # # ...
87
184
  # end
88
185
  # end
89
186
  #
187
+ # @example Instantiating a channel with a :prefetch option
188
+ #
189
+ # AMQP.connect do |connection|
190
+ # AMQP::Channel.new(connection, AMQP::Channel.next_channel_id, :prefetch => 5) do |channel, open_ok|
191
+ # # ...
192
+ # end
193
+ # end
194
+ #
195
+ #
196
+ # @option options [Boolean] :prefetch (nil) Specifies number of messages to prefetch. Channel-specific. See {AMQP::Channel#prefetch}.
90
197
  #
91
198
  # @yield [channel, open_ok] Yields open channel instance and AMQP method (channel.open-ok) instance. The latter is optional.
92
199
  # @yieldparam [Channel] channel Channel that is successfully open
93
200
  # @yieldparam [AMQP::Protocol::Channel::OpenOk] open_ok AMQP channel.open-ok) instance
94
201
  #
95
202
  #
203
+ # @see AMQP::Channel#prefetch
96
204
  # @api public
97
- def initialize(connection = nil, id = self.class.next_channel_id, &block)
205
+ def initialize(connection = nil, id = self.class.next_channel_id, options = {}, &block)
98
206
  raise 'AMQP can only be used from within EM.run {}' unless EM.reactor_running?
99
207
 
100
- @connection = connection || AMQP.start
208
+ @connection = connection || AMQP.connection || AMQP.start
101
209
 
102
210
  super(@connection, id)
103
211
 
@@ -127,14 +235,24 @@ module AMQP
127
235
  else block.call(ch, open_ok)
128
236
  end # case
129
237
  end # if
238
+
239
+ self.prefetch(options[:prefetch], false) if options[:prefetch]
130
240
  end # self.open
131
241
  end # @connection.on_open
132
242
  end
133
243
 
134
-
244
+ # Takes a block that will be deferred till the moment when channel is considered open
245
+ # (channel.open-ok is received from the broker). If you need to delay an operation
246
+ # till the moment channel is open, this method is what you are looking for.
247
+ #
248
+ # Multiple callbacks are supported. If when this moment is called, channel is already
249
+ # open, block is executed immediately.
250
+ #
251
+ # @api public
135
252
  def once_open(&block)
136
253
  @channel_is_open_deferrable.callback(&block)
137
254
  end # once_open(&block)
255
+ alias once_opened once_open
138
256
 
139
257
 
140
258
  # Defines, intializes and returns a direct Exchange instance.
@@ -598,21 +716,7 @@ module AMQP
598
716
 
599
717
  queue
600
718
  else
601
- queue = if block.nil?
602
- Queue.new(self, name, opts)
603
- else
604
- shim = Proc.new { |q, method|
605
- queue = find_queue(method.queue)
606
- if block.arity == 1
607
- block.call(queue)
608
- else
609
- block.call(queue, method.consumer_count, method.message_count)
610
- end
611
- }
612
- Queue.new(self, name, opts, &shim)
613
- end
614
-
615
- register_queue(queue)
719
+ self.queue!(name, opts, &block)
616
720
  end
617
721
  end
618
722
 
@@ -623,10 +727,30 @@ module AMQP
623
727
  self.status == :opened || self.status == :opening
624
728
  end # open?
625
729
 
626
-
730
+ # Same as {Channel#queue} but when queue with the same name already exists in this channel
731
+ # object's cache, this method will replace existing queue with a newly defined one. Consider
732
+ # using {Channel#queue} instead.
733
+ #
734
+ # @see Channel#queue
735
+ #
736
+ # @return [Queue]
737
+ # @api public
627
738
  def queue!(name, opts = {}, &block)
628
- # TODO
629
- raise NotImplementedError.new
739
+ queue = if block.nil?
740
+ Queue.new(self, name, opts)
741
+ else
742
+ shim = Proc.new { |q, method|
743
+ queue = find_queue(method.queue)
744
+ if block.arity == 1
745
+ block.call(queue)
746
+ else
747
+ block.call(queue, method.consumer_count, method.message_count)
748
+ end
749
+ }
750
+ Queue.new(self, name, opts, &shim)
751
+ end
752
+
753
+ register_queue(queue)
630
754
  end
631
755
 
632
756
 
@@ -643,7 +767,7 @@ module AMQP
643
767
  # library. See that documentation for further reference.
644
768
  #
645
769
  # When the optional object is not passed, the returned rpc reference is
646
- # used to send messages and arguments to the queue. See #method_missing
770
+ # used to send messages and arguments to the queue. See {AMQP::RPC#method_missing}
647
771
  # which does all of the heavy lifting with the proxy. Some client
648
772
  # elsewhere must call this method *with* the optional block so that
649
773
  # there is a valid destination. Failure to do so will just enqueue
@@ -662,25 +786,19 @@ module AMQP
662
786
  end
663
787
 
664
788
 
665
- # Define a callback to be run on channel-level exception.
666
- #
667
- # @param [String] msg Error message
668
- #
669
- # @api public
670
- def self.error(msg = nil, &block)
671
- # TODO
672
- raise NotImplementedError.new
673
- end
674
789
 
675
- # @param [Fixnum] size
790
+
791
+ # @param [Fixnum] Message count
676
792
  # @param [Boolean] global (false)
677
793
  #
678
794
  # @return [Channel] self
679
795
  #
680
796
  # @api public
681
- def prefetch(size, global = false, &block)
682
- # RabbitMQ as of 2.3.1 does not support prefetch_size.
683
- self.qos(0, size, global, &block)
797
+ def prefetch(count, global = false, &block)
798
+ self.once_open do
799
+ # RabbitMQ as of 2.3.1 does not support prefetch_size.
800
+ self.qos(0, count, global, &block)
801
+ end
684
802
 
685
803
  self
686
804
  end
@@ -703,6 +821,45 @@ module AMQP
703
821
  #
704
822
 
705
823
 
824
+ # Defines a global callback to be run on channel-level exception across
825
+ # all channels. Consider using Channel#on_error instead. This method is here for sake
826
+ # of backwards compatibility with 0.6.x and 0.7.x releases.
827
+ #
828
+ # @param [String] msg Error message that passed to previously defined handler
829
+ #
830
+ # @deprecated
831
+ # @api public
832
+ # @private
833
+ def self.error(msg = nil, &block)
834
+ if block
835
+ @global_error_handler = block
836
+ else
837
+ @global_error_handler.call(msg) if @global_error_handler && msg
838
+ end
839
+ end
840
+
841
+ # Defines a global callback to be run on channel-level exception across
842
+ # all channels. Consider using Channel#on_error instead. This method is here for sake
843
+ # of backwards compatibility with 0.6.x and 0.7.x releases.
844
+ # @see AMQP::Channel#on_error
845
+ # @deprecated
846
+ # @api public
847
+ def self.on_error(&block)
848
+ self.error(&block)
849
+ end # self.on_error(&block)
850
+
851
+ # Overrides AMQ::Client::Channel version to also call global callback
852
+ # (if defined) for backwards compatibility.
853
+ #
854
+ # @private
855
+ # @api private
856
+ def handle_close(_, exception = nil)
857
+ super(_, exception)
858
+
859
+ self.class.error(exception.message)
860
+ end
861
+
862
+
706
863
  # Resets channel state (for example, list of registered queue objects and so on).
707
864
  #
708
865
  # Most of the time, this method is not
@@ -711,19 +868,40 @@ module AMQP
711
868
  # @private
712
869
  # @api plugin
713
870
  def reset
714
- # TODO
715
- raise NotImplementedError.new
871
+ # See AMQ::Client::Channel
872
+ self.reset_state!
873
+
874
+ # there is no way to reset a deferrable; we have to use a new instance. MK.
875
+ @channel_is_open_deferrable = AMQ::Client::EventMachineClient::Deferrable.new
716
876
  end
717
877
 
878
+ # @private
879
+ # @api plugin
880
+ def reset_state!
881
+ super
882
+ @rpcs = Hash.new
883
+ end # reset_state!
884
+
885
+
886
+ # Overrides superclass method to also re-create @channel_is_open_deferrable
887
+ #
888
+ # @api plugin
889
+ # @private
890
+ def handle_connection_interruption(exception = nil)
891
+ super(exception)
892
+ @channel_is_open_deferrable = AMQ::Client::EventMachineClient::Deferrable.new
893
+ end
894
+
895
+
718
896
  # @private
719
897
  # @api private
720
898
  def self.channel_id_mutex
721
899
  @channel_id_mutex ||= Mutex.new
722
900
  end
723
901
 
902
+ # Returns incrementing channel id. This method is thread safe.
724
903
  # @return [Fixnum]
725
- # @private
726
- # @api private
904
+ # @api public
727
905
  def self.next_channel_id
728
906
  channel_id_mutex.synchronize do
729
907
  @last_channel_id ||= 0
@@ -750,6 +928,7 @@ module AMQP
750
928
 
751
929
  protected
752
930
 
931
+ # @private
753
932
  def validate_parameters_match!(entity, parameters)
754
933
  unless entity.opts == parameters || parameters[:passive]
755
934
  raise AMQP::IncompatibleOptionsError.new(entity.name, entity.opts, parameters)
@@ -1,11 +1,15 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "amqp/basic_client"
3
+ require "uri"
4
+ require "amqp/session"
4
5
 
5
6
  module AMQP
7
+ # @private
6
8
  module Client
7
9
 
10
+ # @private
8
11
  AMQP_PORTS = Hash["amqp" => 5672, "amqps" => 5671].freeze
12
+ # @private
9
13
  AMQPS = "amqps".freeze
10
14
 
11
15
 
@@ -60,12 +64,11 @@ module AMQP
60
64
 
61
65
  # @private
62
66
  def self.client
63
- @client_implementation ||= BasicClient
67
+ @client_implementation ||= AMQP::Session
64
68
  end
65
69
 
66
70
  # @private
67
71
  def self.client=(value)
68
72
  @client_implementation = value
69
73
  end
70
-
71
74
  end # AMQP