amqp 0.7.0 → 0.7.1

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.
Files changed (77) hide show
  1. data/.gitignore +3 -2
  2. data/CHANGELOG +25 -0
  3. data/Gemfile +4 -2
  4. data/README.md +2 -0
  5. data/{amqp.todo → TODO} +1 -3
  6. data/amqp.gemspec +3 -3
  7. data/bin/irb +2 -2
  8. data/bin/jenkins.sh +25 -0
  9. data/bin/set_test_suite_realms_up.sh +21 -0
  10. data/doc/EXAMPLE_01_PINGPONG +1 -1
  11. data/doc/EXAMPLE_02_CLOCK +1 -1
  12. data/doc/EXAMPLE_03_STOCKS +1 -1
  13. data/doc/EXAMPLE_04_MULTICLOCK +1 -1
  14. data/doc/EXAMPLE_05_ACK +1 -1
  15. data/doc/EXAMPLE_05_POP +1 -1
  16. data/doc/EXAMPLE_06_HASHTABLE +1 -1
  17. data/examples/{mq/ack.rb → ack.rb} +6 -6
  18. data/examples/{mq/automatic_binding_for_default_direct_exchange.rb → automatic_binding_for_default_direct_exchange.rb} +4 -4
  19. data/examples/{mq/callbacks.rb → callbacks.rb} +2 -2
  20. data/examples/{mq/clock.rb → clock.rb} +5 -5
  21. data/examples/{mq/hashtable.rb → hashtable.rb} +4 -4
  22. data/examples/{mq/internal.rb → internal.rb} +5 -5
  23. data/examples/{mq/logger.rb → logger.rb} +5 -5
  24. data/examples/{mq/multiclock.rb → multiclock.rb} +4 -4
  25. data/examples/{mq/pingpong.rb → pingpong.rb} +5 -5
  26. data/examples/{mq/pop.rb → pop.rb} +3 -3
  27. data/examples/{mq/primes-simple.rb → primes-simple.rb} +0 -0
  28. data/examples/{mq/primes.rb → primes.rb} +6 -6
  29. data/examples/{amqp/simple.rb → simple.rb} +1 -1
  30. data/examples/{mq/stocks.rb → stocks.rb} +5 -5
  31. data/lib/amqp.rb +8 -112
  32. data/lib/amqp/basic_client.rb +58 -0
  33. data/lib/amqp/channel.rb +937 -0
  34. data/lib/amqp/client.rb +72 -79
  35. data/lib/{mq → amqp}/collection.rb +12 -2
  36. data/lib/amqp/connection.rb +115 -0
  37. data/lib/amqp/exceptions.rb +18 -0
  38. data/lib/{mq → amqp}/exchange.rb +32 -34
  39. data/lib/{ext → amqp/ext}/em.rb +1 -1
  40. data/lib/{ext → amqp/ext}/emfork.rb +0 -0
  41. data/lib/amqp/frame.rb +3 -3
  42. data/lib/{mq → amqp}/header.rb +5 -11
  43. data/lib/{mq → amqp}/logger.rb +2 -2
  44. data/lib/amqp/protocol.rb +2 -2
  45. data/lib/{mq → amqp}/queue.rb +20 -17
  46. data/lib/{mq → amqp}/rpc.rb +20 -8
  47. data/lib/amqp/server.rb +1 -1
  48. data/lib/amqp/version.rb +1 -1
  49. data/lib/mq.rb +20 -964
  50. data/protocol/codegen.rb +1 -1
  51. data/research/api.rb +3 -3
  52. data/research/primes-forked.rb +5 -5
  53. data/research/primes-processes.rb +5 -5
  54. data/research/primes-threaded.rb +5 -5
  55. data/spec/integration/authentication_spec.rb +114 -0
  56. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +13 -12
  57. data/spec/{unit/mq → integration}/channel_close_spec.rb +2 -2
  58. data/spec/{unit/mq → integration}/exchange_declaration_spec.rb +26 -14
  59. data/spec/{unit/mq → integration}/queue_declaration_spec.rb +4 -4
  60. data/spec/integration/queue_exclusivity_spec.rb +95 -0
  61. data/spec/integration/reply_queue_communication_spec.rb +63 -0
  62. data/spec/integration/store_and_forward_spec.rb +121 -0
  63. data/spec/integration/topic_subscription_spec.rb +193 -0
  64. data/spec/integration/workload_distribution_spec.rb +245 -0
  65. data/spec/spec_helper.rb +16 -32
  66. data/spec/unit/{mq/mq_basic_spec.rb → amqp/basic_spec.rb} +4 -4
  67. data/spec/unit/{mq → amqp}/collection_spec.rb +22 -7
  68. data/spec/unit/amqp/connection_spec.rb +116 -0
  69. data/spec/unit/amqp/frame_spec.rb +18 -18
  70. data/spec/unit/amqp/protocol_spec.rb +9 -11
  71. metadata +54 -49
  72. data/lib/ext/blankslate.rb +0 -9
  73. data/spec/mq_helper.rb +0 -70
  74. data/spec/unit/amqp/client_spec.rb +0 -472
  75. data/spec/unit/amqp/misc_spec.rb +0 -123
  76. data/spec/unit/mq/misc_spec.rb +0 -228
  77. data/spec/unit/mq/queue_spec.rb +0 -71
@@ -7,4 +7,4 @@ rescue LoadError
7
7
  require 'eventmachine'
8
8
  end
9
9
 
10
- require 'ext/emfork'
10
+ require 'amqp/ext/emfork'
File without changes
@@ -1,8 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.expand_path('../spec', __FILE__)
4
- require File.expand_path('../buffer', __FILE__)
5
- require File.expand_path('../protocol', __FILE__)
3
+ require "amqp/spec"
4
+ require "amqp/buffer"
5
+ require "amqp/protocol"
6
6
 
7
7
  module AMQP
8
8
  class Frame #:nodoc: all
@@ -1,9 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
3
+ module AMQP
4
4
  class Header
5
- include AMQP
6
-
7
5
  def initialize(mq, header_obj)
8
6
  @mq = mq
9
7
  @header = header_obj
@@ -16,16 +14,12 @@ class MQ
16
14
  }
17
15
  end
18
16
 
19
- # Reject this message (XXX currently unimplemented in rabbitmq)
17
+ # Reject this message.
20
18
  # * :requeue => true | false (default false)
21
19
  def reject(opts = {})
22
- if @mq.broker.server_properties[:product] == "RabbitMQ"
23
- raise NotImplementedError.new("RabbitMQ doesn't implement the Basic.Reject method\nSee http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2009-February/002853.html")
24
- else
25
- @mq.callback {
26
- @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
27
- }
28
- end
20
+ @mq.callback {
21
+ @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
22
+ }
29
23
  end
30
24
 
31
25
  def method_missing(meth, *args, &blk)
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
3
+ module AMQP
4
4
  class Logger
5
5
  def initialize(*args, &block)
6
6
  opts = args.pop if args.last.is_a? Hash
@@ -51,7 +51,7 @@ class MQ
51
51
 
52
52
  print(opts)
53
53
  unless Logger.disabled?
54
- MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
54
+ AMQP::Channel.fanout('logging', :durable => true).publish Marshal.dump(opts)
55
55
  end
56
56
 
57
57
  opts
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.expand_path('../spec', __FILE__)
4
- require File.expand_path('../buffer', __FILE__)
3
+ require "amqp/spec"
4
+ require "amqp/buffer"
5
5
 
6
6
  module AMQP
7
7
  module Protocol
@@ -1,9 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
3
+ module AMQP
4
4
  class Queue
5
- include AMQP
6
-
7
5
  def self.add_default_options(name, opts, block)
8
6
  { :queue => name, :nowait => block.nil? }.merge(opts)
9
7
  end
@@ -14,7 +12,7 @@ class MQ
14
12
  #
15
13
  # Like an Exchange, queue names starting with 'amq.' are reserved for
16
14
  # internal use. Attempts to create queue names in violation of this
17
- # reservation will raise MQ:Error (ACCESS_REFUSED).
15
+ # reservation will raise AMQP::Error (ACCESS_REFUSED).
18
16
  #
19
17
  # When a queue is created without a name, the server will generate a
20
18
  # unique name internally (not currently supported in this library).
@@ -47,7 +45,7 @@ class MQ
47
45
  # from this queue.
48
46
  #
49
47
  # Attempting to redeclare an already-declared queue as :exclusive => true
50
- # will raise MQ:Error.
48
+ # will raise AMQP::Error.
51
49
  #
52
50
  # * :auto_delete = true | false (default false)
53
51
  # If set, the queue is deleted when all consumers have finished
@@ -78,6 +76,8 @@ class MQ
78
76
  }
79
77
 
80
78
  self.callback = block
79
+
80
+ block.call(self) if @opts[:nowait] && block
81
81
  end
82
82
 
83
83
  attr_reader :name, :sync_bind
@@ -90,8 +90,8 @@ class MQ
90
90
  #
91
91
  # A valid exchange name (or reference) must be passed as the first
92
92
  # parameter. Both of these are valid:
93
- # exch = MQ.direct('foo exchange')
94
- # queue = MQ.queue('bar queue')
93
+ # exch = AMQP::Channel.direct('foo exchange')
94
+ # queue = AMQP::Channel.queue('bar queue')
95
95
  # queue.bind('foo.exchange') # OR
96
96
  # queue.bind(exch)
97
97
  #
@@ -129,6 +129,9 @@ class MQ
129
129
  :nowait => block.nil? }.merge(opts))
130
130
  }
131
131
  self.bind_callback = block
132
+
133
+ block.call(self) if opts[:nowait] && block
134
+
132
135
  self
133
136
  end
134
137
 
@@ -206,14 +209,14 @@ class MQ
206
209
  # The provided block is passed a single message each time pop is called.
207
210
  #
208
211
  # EM.run do
209
- # exchange = MQ.direct("foo queue")
212
+ # exchange = AMQP::Channel.direct("foo queue")
210
213
  # EM.add_periodic_timer(1) do
211
214
  # exchange.publish("random number #{rand(1000)}")
212
215
  # end
213
216
  #
214
217
  # # note that #bind is never called; it is implicit because
215
218
  # # the exchange and queue names match
216
- # queue = MQ.queue('foo queue')
219
+ # queue = AMQP::Channel.queue('foo queue')
217
220
  # queue.pop { |body| puts "received payload [#{body}]" }
218
221
  #
219
222
  # EM.add_periodic_timer(1) { queue.pop }
@@ -224,12 +227,12 @@ class MQ
224
227
  # AMQP::Protocol::Header.
225
228
  #
226
229
  # EM.run do
227
- # exchange = MQ.direct("foo queue")
230
+ # exchange = AMQP::Channel.direct("foo queue")
228
231
  # EM.add_periodic_timer(1) do
229
232
  # exchange.publish("random number #{rand(1000)}")
230
233
  # end
231
234
  #
232
- # queue = MQ.queue('foo queue')
235
+ # queue = AMQP::Channel.queue('foo queue')
233
236
  # queue.pop do |header, body|
234
237
  # p header
235
238
  # puts "received payload [#{body}]"
@@ -277,12 +280,12 @@ class MQ
277
280
  # exchange matches a message to this queue.
278
281
  #
279
282
  # EM.run do
280
- # exchange = MQ.direct("foo queue")
283
+ # exchange = AMQP::Channel.direct("foo queue")
281
284
  # EM.add_periodic_timer(1) do
282
285
  # exchange.publish("random number #{rand(1000)}")
283
286
  # end
284
287
  #
285
- # queue = MQ.queue('foo queue')
288
+ # queue = AMQP::Channel.queue('foo queue')
286
289
  # queue.subscribe { |body| puts "received payload [#{body}]" }
287
290
  # end
288
291
  #
@@ -291,14 +294,14 @@ class MQ
291
294
  # AMQP::Protocol::Header.
292
295
  #
293
296
  # EM.run do
294
- # exchange = MQ.direct("foo queue")
297
+ # exchange = AMQP::Channel.direct("foo queue")
295
298
  # EM.add_periodic_timer(1) do
296
299
  # exchange.publish("random number #{rand(1000)}")
297
300
  # end
298
301
  #
299
302
  # # note that #bind is never called; it is implicit because
300
303
  # # the exchange and queue names match
301
- # queue = MQ.queue('foo queue')
304
+ # queue = AMQP::Channel.queue('foo queue')
302
305
  # queue.subscribe do |header, body|
303
306
  # p header
304
307
  # puts "received payload [#{body}]"
@@ -398,7 +401,7 @@ class MQ
398
401
  # the headers parameter. See #pop or #subscribe for a code example.
399
402
  #
400
403
  def receive(headers, body)
401
- headers = MQ::Header.new(@mq, headers) unless headers.nil?
404
+ headers = AMQP::Header.new(@mq, headers) unless headers.nil?
402
405
 
403
406
  if cb = (@on_msg || @on_pop)
404
407
  cb.call *(cb.arity == 1 ? [body] : [headers, body])
@@ -407,7 +410,7 @@ class MQ
407
410
 
408
411
  # Get the number of messages and consumers on a queue.
409
412
  #
410
- # MQ.queue('name').status { |num_messages, num_consumers|
413
+ # AMQP::Channel.queue('name').status { |num_messages, num_consumers|
411
414
  # puts num_messages
412
415
  # }
413
416
  #
@@ -1,14 +1,26 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
3
+ module AMQP
4
+
5
+ # encoding: utf-8
6
+
7
+ if defined?(BasicObject)
8
+ BlankSlate = BasicObject
9
+ else
10
+ class BlankSlate #:nodoc:
11
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
12
+ end
13
+ end
14
+
15
+
4
16
  # Basic RPC (remote procedure call) facility.
5
17
  #
6
18
  # Needs more detail and explanation.
7
19
  #
8
20
  # EM.run do
9
- # server = MQ.rpc('hash table node', Hash)
21
+ # server = AMQP::Channel.new.rpc('hash table node', Hash)
10
22
  #
11
- # client = MQ.rpc('hash table node')
23
+ # client = AMQP::Channel.new.rpc('hash table node')
12
24
  # client[:now] = Time.now
13
25
  # client[:one] = 1
14
26
  #
@@ -22,7 +34,7 @@ class MQ
22
34
  # end
23
35
  # end
24
36
  #
25
- class RPC < BlankSlate
37
+ class RPC < ::AMQP::BlankSlate
26
38
  # Takes a channel, queue and optional object.
27
39
  #
28
40
  # The optional object may be a class name, module name or object
@@ -63,7 +75,7 @@ class MQ
63
75
  info.ack
64
76
 
65
77
  if info.reply_to
66
- @mq.queue(info.reply_to).publish(::Marshal.dump(ret), :key => info.reply_to, :message_id => info.message_id)
78
+ @mq.queue(info.reply_to, :auto_delete => true).publish(::Marshal.dump(ret), :key => info.reply_to, :message_id => info.message_id)
67
79
  end
68
80
  }
69
81
  else
@@ -78,13 +90,13 @@ class MQ
78
90
  end
79
91
  end
80
92
 
81
- # Calling MQ.rpc(*args) returns a proxy object without any methods beyond
93
+ # Calling AMQP::Channel.rpc(*args) returns a proxy object without any methods beyond
82
94
  # those in Object. All calls to the proxy are handled by #method_missing which
83
95
  # works to marshal and unmarshal all method calls and their arguments.
84
96
  #
85
97
  # EM.run do
86
- # server = MQ.rpc('hash table node', Hash)
87
- # client = MQ.rpc('hash table node')
98
+ # server = AMQP::Channel.new.rpc('hash table node', Hash)
99
+ # client = AMQP::Channel.new.rpc('hash table node')
88
100
  #
89
101
  # # calls #method_missing on #[] which marshals the method name and
90
102
  # # arguments to publish them to the remote
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.expand_path('../frame', __FILE__)
3
+ require "amqp/frame"
4
4
 
5
5
  module AMQP
6
6
  module Server
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module AMQP
4
- VERSION = '0.7.0'
4
+ VERSION = '0.7.1'
5
5
  end
data/lib/mq.rb CHANGED
@@ -1,975 +1,31 @@
1
- # encoding: utf-8
1
+ $stdout.puts <<-MESSAGE
2
+ -------------------------------------------------------------------------------------
3
+ DEPRECATION WARNING!
2
4
 
3
- #:main: README
4
- #
5
+ Use of mq.rb is deprecated. Instead of
5
6
 
6
- $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
7
- require 'amqp'
8
- require 'mq/collection'
7
+ require "mq"
9
8
 
10
- class MQ
11
- %w[ exchange queue rpc header ].each do |file|
12
- require "mq/#{file}"
13
- end
9
+ please use
14
10
 
15
- class << self
16
- @logging = false
17
- attr_accessor :logging
18
- end
11
+ require "amqp"
19
12
 
20
- # Raised whenever an illegal operation is attempted.
21
- class Error < StandardError; end
22
13
 
23
- class IncompatibleOptionsError < Error
24
- def initialize(name, opts_1, opts_2)
25
- super("There is already an instance called #{name} with options #{opts_1.inspect}, you can't define the same instance with different options (#{opts_2.inspect})!")
26
- end
27
- end
14
+ mq.rb will be REMOVED in AMQP gem version 1.0.
28
15
 
29
- class ChannelClosedError < Error
30
- def initialize(instance)
31
- super("The channel #{instance.channel} was closed, you can't use it anymore!")
32
- end
33
- end
34
- end
16
+ Why is it deprecated? Because it was a poor name choice all along. We better not
17
+ release 1.0 with it. Both mq.rb and MQ class step away from AMQP terminology and
18
+ make 8 out of 10 engineers think it has something to do with AMQP queues (in fact,
19
+ MQ should have been called Channel all along). No other AMQP client library we know
20
+ of invents it's own terminology when it comes to AMQP entities, and amqp gem shouldn't,
21
+ too.
35
22
 
36
- # The top-level class for building AMQP clients. This class contains several
37
- # convenience methods for working with queues and exchanges. Many calls
38
- # delegate/forward to subclasses, but this is the preferred API. The subclass
39
- # API is subject to change while this high-level API will likely remain
40
- # unchanged as the library evolves. All code examples will be written using
41
- # the MQ API.
42
- #
43
- # Below is a somewhat complex example that demonstrates several capabilities
44
- # of the library. The example starts a clock using a +fanout+ exchange which
45
- # is used for 1 to many communications. Each consumer generates a queue to
46
- # receive messages and do some operation (in this case, print the time).
47
- # One consumer prints messages every second while the second consumer prints
48
- # messages every 2 seconds. After 5 seconds has elapsed, the 1 second
49
- # consumer is deleted.
50
- #
51
- # Of interest is the relationship of EventMachine to the process. All MQ
52
- # operations must occur within the context of an EM.run block. We start
53
- # EventMachine in its own thread with an empty block; all subsequent calls
54
- # to the MQ API add their blocks to the EM.run block. This demonstrates how
55
- # the library could be used to build up and tear down communications outside
56
- # the context of an EventMachine block and/or integrate the library with
57
- # other synchronous operations. See the EventMachine documentation for
58
- # more information.
59
- #
60
- # require 'rubygems'
61
- # require 'mq'
62
- #
63
- # thr = Thread.new { EM.run }
64
- #
65
- # # turns on extreme logging
66
- # #AMQP.logging = true
67
- #
68
- # def log *args
69
- # p args
70
- # end
71
- #
72
- # def publisher
73
- # clock = MQ.fanout('clock')
74
- # EM.add_periodic_timer(1) do
75
- # puts
76
- #
77
- # log :publishing, time = Time.now
78
- # clock.publish(Marshal.dump(time))
79
- # end
80
- # end
81
- #
82
- # def one_second_consumer
83
- # MQ.queue('every second').bind(MQ.fanout('clock')).subscribe do |time|
84
- # log 'every second', :received, Marshal.load(time)
85
- # end
86
- # end
87
- #
88
- # def two_second_consumer
89
- # MQ.queue('every 2 seconds').bind('clock').subscribe do |time|
90
- # time = Marshal.load(time)
91
- # log 'every 2 seconds', :received, time if time.sec % 2 == 0
92
- # end
93
- # end
94
- #
95
- # def delete_one_second
96
- # EM.add_timer(5) do
97
- # # delete the 'every second' queue
98
- # log 'Deleting [every second] queue'
99
- # MQ.queue('every second').delete
100
- # end
101
- # end
102
- #
103
- # publisher
104
- # one_second_consumer
105
- # two_second_consumer
106
- # delete_one_second
107
- # thr.join
108
- #
109
- # __END__
110
- #
111
- # [:publishing, Tue Jan 06 22:46:14 -0600 2009]
112
- # ["every second", :received, Tue Jan 06 22:46:14 -0600 2009]
113
- # ["every 2 seconds", :received, Tue Jan 06 22:46:14 -0600 2009]
114
- #
115
- # [:publishing, Tue Jan 06 22:46:16 -0600 2009]
116
- # ["every second", :received, Tue Jan 06 22:46:16 -0600 2009]
117
- # ["every 2 seconds", :received, Tue Jan 06 22:46:16 -0600 2009]
118
- #
119
- # [:publishing, Tue Jan 06 22:46:17 -0600 2009]
120
- # ["every second", :received, Tue Jan 06 22:46:17 -0600 2009]
121
- #
122
- # [:publishing, Tue Jan 06 22:46:18 -0600 2009]
123
- # ["every second", :received, Tue Jan 06 22:46:18 -0600 2009]
124
- # ["every 2 seconds", :received, Tue Jan 06 22:46:18 -0600 2009]
125
- # ["Deleting [every second] queue"]
126
- #
127
- # [:publishing, Tue Jan 06 22:46:19 -0600 2009]
128
- #
129
- # [:publishing, Tue Jan 06 22:46:20 -0600 2009]
130
- # ["every 2 seconds", :received, Tue Jan 06 22:46:20 -0600 2009]
131
- #
132
- class MQ
23
+ If you disagree with this really strongly, let us know by opening an issue at
24
+ https://github.com/ruby-amqp/amqp/issues
133
25
 
134
- #
135
- # Behaviors
136
- #
26
+ Thank you for understanding. AMQP gem maintainers team.
137
27
 
138
- include AMQP
139
- include EM::Deferrable
28
+ -------------------------------------------------------------------------------------
29
+ MESSAGE
140
30
 
141
-
142
-
143
- #
144
- # API
145
- #
146
-
147
- # Returns a new channel. A channel is a bidirectional virtual
148
- # connection between the client and the AMQP server. Elsewhere in the
149
- # library the channel is referred to in parameter lists as +mq+.
150
- #
151
- # Optionally takes the result from calling AMQP::connect.
152
- #
153
- # Rarely called directly by client code. This is implicitly called
154
- # by most instance methods. See #method_missing.
155
- #
156
- # EM.run do
157
- # channel = MQ.new
158
- # end
159
- #
160
- # EM.run do
161
- # channel = MQ.new AMQP::connect
162
- # end
163
- #
164
- def initialize(connection = nil)
165
- raise 'MQ can only be used from within EM.run {}' unless EM.reactor_running?
166
-
167
- @_send_mutex = Mutex.new
168
- @get_queue_mutex = Mutex.new
169
-
170
- @connection = connection || AMQP.start
171
-
172
- conn.callback { |c|
173
- @channel = c.add_channel(self)
174
- send Protocol::Channel::Open.new
175
- }
176
- end
177
-
178
- attr_reader :channel, :connection, :status
179
- alias :conn :connection
180
-
181
- def closed?
182
- @status.eql?(:closed)
183
- end
184
-
185
-
186
-
187
- # Defines, intializes and returns an Exchange to act as an ingress
188
- # point for all published messages.
189
- #
190
- # == Direct
191
- # A direct exchange is useful for 1:1 communication between a publisher and
192
- # subscriber. Messages are routed to the queue with a binding that shares
193
- # the same name as the exchange. Alternately, the messages are routed to
194
- # the bound queue that shares the same name as the routing key used for
195
- # defining the exchange. This exchange type does not honor the +:key+ option
196
- # when defining a new instance with a name. It _will_ honor the +:key+ option
197
- # if the exchange name is the empty string.
198
- # Allocating this exchange without a name _or_ with the empty string
199
- # will use the internal 'amq.direct' exchange.
200
- #
201
- # Any published message, regardless of its persistence setting, is thrown
202
- # away by the exchange when there are no queues bound to it.
203
- #
204
- # # exchange is named 'foo'
205
- # exchange = MQ.direct('foo')
206
- #
207
- # # or, the exchange can use the default name (amq.direct) and perform
208
- # # routing comparisons using the :key
209
- # exchange = MQ.direct("", :key => 'foo')
210
- # exchange.publish('some data') # will be delivered to queue bound to 'foo'
211
- #
212
- # queue = MQ.queue('foo')
213
- # # can receive data since the queue name and the exchange key match exactly
214
- # queue.pop { |data| puts "received data [#{data}]" }
215
- #
216
- # == Options
217
- # * :passive => true | false (default false)
218
- # If set, the server will not create the exchange if it does not
219
- # already exist. The client can use this to check whether an exchange
220
- # exists without modifying the server state.
221
- #
222
- # * :durable => true | false (default false)
223
- # If set when creating a new exchange, the exchange will be marked as
224
- # durable. Durable exchanges remain active when a server restarts.
225
- # Non-durable exchanges (transient exchanges) are purged if/when a
226
- # server restarts.
227
- #
228
- # A transient exchange (the default) is stored in memory-only. The
229
- # exchange and all bindings will be lost on a server restart.
230
- # It makes no sense to publish a persistent message to a transient
231
- # exchange.
232
- #
233
- # Durable exchanges and their bindings are recreated upon a server
234
- # restart. Any published messages not routed to a bound queue are lost.
235
- #
236
- # * :auto_delete => true | false (default false)
237
- # If set, the exchange is deleted when all queues have finished
238
- # using it. The server waits for a short period of time before
239
- # determining the exchange is unused to give time to the client code
240
- # to bind a queue to it.
241
- #
242
- # If the exchange has been previously declared, this option is ignored
243
- # on subsequent declarations.
244
- #
245
- # * :internal => true | false (default false)
246
- # If set, the exchange may not be used directly by publishers, but
247
- # only when bound to other exchanges. Internal exchanges are used to
248
- # construct wiring that is not visible to applications.
249
- #
250
- # * :nowait => true | false (default true)
251
- # If set, the server will not respond to the method. The client should
252
- # not wait for a reply method. If the server could not complete the
253
- # method it will raise a channel or connection exception.
254
- #
255
- # == Exceptions
256
- # Doing any of these activities are illegal and will raise MQ:Error.
257
- # * redeclare an already-declared exchange to a different type
258
- # * :passive => true and the exchange does not exist (NOT_FOUND)
259
- #
260
- def direct(name = 'amq.direct', opts = {}, &block)
261
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
262
- extended_opts = Exchange.add_default_options(:direct, name, opts, block)
263
-
264
- validate_parameters_match!(exchange, extended_opts)
265
-
266
- exchange
267
- else
268
- self.exchanges << Exchange.new(self, :direct, name, opts, &block)
269
- end
270
- end
271
-
272
- # Defines, intializes and returns an Exchange to act as an ingress
273
- # point for all published messages.
274
- #
275
- # == Fanout
276
- # A fanout exchange is useful for 1:N communication where one publisher
277
- # feeds multiple subscribers. Like direct exchanges, messages published
278
- # to a fanout exchange are delivered to queues whose name matches the
279
- # exchange name (or are bound to that exchange name). Each queue gets
280
- # its own copy of the message.
281
- #
282
- # Any published message, regardless of its persistence setting, is thrown
283
- # away by the exchange when there are no queues bound to it.
284
- #
285
- # Like the direct exchange type, this exchange type does not honor the
286
- # +:key+ option when defining a new instance with a name. It _will_ honor
287
- # the +:key+ option if the exchange name is the empty string.
288
- # Allocating this exchange without a name _or_ with the empty string
289
- # will use the internal 'amq.fanout' exchange.
290
- #
291
- # EM.run do
292
- # clock = MQ.fanout('clock')
293
- # EM.add_periodic_timer(1) do
294
- # puts "\npublishing #{time = Time.now}"
295
- # clock.publish(Marshal.dump(time))
296
- # end
297
- #
298
- # amq = MQ.queue('every second')
299
- # amq.bind(MQ.fanout('clock')).subscribe do |time|
300
- # puts "every second received #{Marshal.load(time)}"
301
- # end
302
- #
303
- # # note the string passed to #bind
304
- # MQ.queue('every 5 seconds').bind('clock').subscribe do |time|
305
- # time = Marshal.load(time)
306
- # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
307
- # end
308
- # end
309
- #
310
- # == Options
311
- # * :passive => true | false (default false)
312
- # If set, the server will not create the exchange if it does not
313
- # already exist. The client can use this to check whether an exchange
314
- # exists without modifying the server state.
315
- #
316
- # * :durable => true | false (default false)
317
- # If set when creating a new exchange, the exchange will be marked as
318
- # durable. Durable exchanges remain active when a server restarts.
319
- # Non-durable exchanges (transient exchanges) are purged if/when a
320
- # server restarts.
321
- #
322
- # A transient exchange (the default) is stored in memory-only. The
323
- # exchange and all bindings will be lost on a server restart.
324
- # It makes no sense to publish a persistent message to a transient
325
- # exchange.
326
- #
327
- # Durable exchanges and their bindings are recreated upon a server
328
- # restart. Any published messages not routed to a bound queue are lost.
329
- #
330
- # * :auto_delete => true | false (default false)
331
- # If set, the exchange is deleted when all queues have finished
332
- # using it. The server waits for a short period of time before
333
- # determining the exchange is unused to give time to the client code
334
- # to bind a queue to it.
335
- #
336
- # If the exchange has been previously declared, this option is ignored
337
- # on subsequent declarations.
338
- #
339
- # * :internal => true | false (default false)
340
- # If set, the exchange may not be used directly by publishers, but
341
- # only when bound to other exchanges. Internal exchanges are used to
342
- # construct wiring that is not visible to applications.
343
- #
344
- # * :nowait => true | false (default true)
345
- # If set, the server will not respond to the method. The client should
346
- # not wait for a reply method. If the server could not complete the
347
- # method it will raise a channel or connection exception.
348
- #
349
- # == Exceptions
350
- # Doing any of these activities are illegal and will raise MQ:Error.
351
- # * redeclare an already-declared exchange to a different type
352
- # * :passive => true and the exchange does not exist (NOT_FOUND)
353
- #
354
- def fanout(name = 'amq.fanout', opts = {}, &block)
355
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
356
- extended_opts = Exchange.add_default_options(:fanout, name, opts, block)
357
-
358
- validate_parameters_match!(exchange, extended_opts)
359
-
360
- exchange
361
- else
362
- self.exchanges << Exchange.new(self, :fanout, name, opts, &block)
363
- end
364
- end
365
-
366
- # Defines, intializes and returns an Exchange to act as an ingress
367
- # point for all published messages.
368
- #
369
- # == Topic
370
- # A topic exchange allows for messages to be published to an exchange
371
- # tagged with a specific routing key. The Exchange uses the routing key
372
- # to determine which queues to deliver the message. Wildcard matching
373
- # is allowed. The topic must be declared using dot notation to separate
374
- # each subtopic.
375
- #
376
- # This is the only exchange type to honor the +key+ hash key for all
377
- # cases.
378
- #
379
- # Any published message, regardless of its persistence setting, is thrown
380
- # away by the exchange when there are no queues bound to it.
381
- #
382
- # As part of the AMQP standard, each server _should_ predeclare a topic
383
- # exchange called 'amq.topic' (this is not required by the standard).
384
- # Allocating this exchange without a name _or_ with the empty string
385
- # will use the internal 'amq.topic' exchange.
386
- #
387
- # The classic example is delivering market data. When publishing market
388
- # data for stocks, we may subdivide the stream based on 2
389
- # characteristics: nation code and trading symbol. The topic tree for
390
- # Apple Computer would look like:
391
- # 'stock.us.aapl'
392
- # For a foreign stock, it may look like:
393
- # 'stock.de.dax'
394
- #
395
- # When publishing data to the exchange, bound queues subscribing to the
396
- # exchange indicate which data interests them by passing a routing key
397
- # for matching against the published routing key.
398
- #
399
- # EM.run do
400
- # exch = MQ.topic("stocks")
401
- # keys = ['stock.us.aapl', 'stock.de.dax']
402
- #
403
- # EM.add_periodic_timer(1) do # every second
404
- # puts
405
- # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
406
- # end
407
- #
408
- # # match against one dot-separated item
409
- # MQ.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
410
- # puts "us stock price [#{price}]"
411
- # end
412
- #
413
- # # match against multiple dot-separated items
414
- # MQ.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
415
- # puts "all stocks: price [#{price}]"
416
- # end
417
- #
418
- # # require exact match
419
- # MQ.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
420
- # puts "dax price [#{price}]"
421
- # end
422
- # end
423
- #
424
- # For matching, the '*' (asterisk) wildcard matches against one
425
- # dot-separated item only. The '#' wildcard (hash or pound symbol)
426
- # matches against 0 or more dot-separated items. If none of these
427
- # symbols are used, the exchange performs a comparison looking for an
428
- # exact match.
429
- #
430
- # == Options
431
- # * :passive => true | false (default false)
432
- # If set, the server will not create the exchange if it does not
433
- # already exist. The client can use this to check whether an exchange
434
- # exists without modifying the server state.
435
- #
436
- # * :durable => true | false (default false)
437
- # If set when creating a new exchange, the exchange will be marked as
438
- # durable. Durable exchanges remain active when a server restarts.
439
- # Non-durable exchanges (transient exchanges) are purged if/when a
440
- # server restarts.
441
- #
442
- # A transient exchange (the default) is stored in memory-only. The
443
- # exchange and all bindings will be lost on a server restart.
444
- # It makes no sense to publish a persistent message to a transient
445
- # exchange.
446
- #
447
- # Durable exchanges and their bindings are recreated upon a server
448
- # restart. Any published messages not routed to a bound queue are lost.
449
- #
450
- # * :auto_delete => true | false (default false)
451
- # If set, the exchange is deleted when all queues have finished
452
- # using it. The server waits for a short period of time before
453
- # determining the exchange is unused to give time to the client code
454
- # to bind a queue to it.
455
- #
456
- # If the exchange has been previously declared, this option is ignored
457
- # on subsequent declarations.
458
- #
459
- # * :internal => true | false (default false)
460
- # If set, the exchange may not be used directly by publishers, but
461
- # only when bound to other exchanges. Internal exchanges are used to
462
- # construct wiring that is not visible to applications.
463
- #
464
- # * :nowait => true | false (default true)
465
- # If set, the server will not respond to the method. The client should
466
- # not wait for a reply method. If the server could not complete the
467
- # method it will raise a channel or connection exception.
468
- #
469
- # == Exceptions
470
- # Doing any of these activities are illegal and will raise MQ:Error.
471
- # * redeclare an already-declared exchange to a different type
472
- # * :passive => true and the exchange does not exist (NOT_FOUND)
473
- #
474
- def topic(name = 'amq.topic', opts = {}, &block)
475
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
476
- extended_opts = Exchange.add_default_options(:topic, name, opts, block)
477
-
478
- validate_parameters_match!(exchange, extended_opts)
479
-
480
- exchange
481
- else
482
- self.exchanges << Exchange.new(self, :topic, name, opts, &block)
483
- end
484
- end
485
-
486
- # Defines, intializes and returns an Exchange to act as an ingress
487
- # point for all published messages.
488
- #
489
- # == Headers
490
- # A headers exchange allows for messages to be published to an exchange
491
- #
492
- # Any published message, regardless of its persistence setting, is thrown
493
- # away by the exchange when there are no queues bound to it.
494
- #
495
- # As part of the AMQP standard, each server _should_ predeclare a headers
496
- # exchange called 'amq.match' (this is not required by the standard).
497
- # Allocating this exchange without a name _or_ with the empty string
498
- # will use the internal 'amq.match' exchange.
499
- #
500
- # TODO: The classic example is ...
501
- #
502
- # When publishing data to the exchange, bound queues subscribing to the
503
- # exchange indicate which data interests them by passing arguments
504
- # for matching against the headers in published messages. The
505
- # form of the matching can be controlled by the 'x-match' argument, which
506
- # may be 'any' or 'all'. If unspecified (in RabbitMQ at least), it defaults
507
- # to "all".
508
- #
509
- # A value of 'all' for 'x-match' implies that all values must match (i.e.
510
- # it does an AND of the headers ), while a value of 'any' implies that
511
- # at least one should match (ie. it does an OR).
512
- #
513
- # TODO: document behavior when either the binding or the message is missing
514
- # a header present in the other
515
- #
516
- # TODO: insert example
517
- #
518
- # == Options
519
- # * :passive => true | false (default false)
520
- # If set, the server will not create the exchange if it does not
521
- # already exist. The client can use this to check whether an exchange
522
- # exists without modifying the server state.
523
- #
524
- # * :durable => true | false (default false)
525
- # If set when creating a new exchange, the exchange will be marked as
526
- # durable. Durable exchanges remain active when a server restarts.
527
- # Non-durable exchanges (transient exchanges) are purged if/when a
528
- # server restarts.
529
- #
530
- # A transient exchange (the default) is stored in memory-only. The
531
- # exchange and all bindings will be lost on a server restart.
532
- # It makes no sense to publish a persistent message to a transient
533
- # exchange.
534
- #
535
- # Durable exchanges and their bindings are recreated upon a server
536
- # restart. Any published messages not routed to a bound queue are lost.
537
- #
538
- # * :auto_delete => true | false (default false)
539
- # If set, the exchange is deleted when all queues have finished
540
- # using it. The server waits for a short period of time before
541
- # determining the exchange is unused to give time to the client code
542
- # to bind a queue to it.
543
- #
544
- # If the exchange has been previously declared, this option is ignored
545
- # on subsequent declarations.
546
- #
547
- # * :internal => true | false (default false)
548
- # If set, the exchange may not be used directly by publishers, but
549
- # only when bound to other exchanges. Internal exchanges are used to
550
- # construct wiring that is not visible to applications.
551
- #
552
- # * :nowait => true | false (default true)
553
- # If set, the server will not respond to the method. The client should
554
- # not wait for a reply method. If the server could not complete the
555
- # method it will raise a channel or connection exception.
556
- #
557
- # == Exceptions
558
- # Doing any of these activities are illegal and will raise MQ:Error.
559
- # * redeclare an already-declared exchange to a different type
560
- # * :passive => true and the exchange does not exist (NOT_FOUND)
561
- # * using a value other than "any" or "all" for "x-match"
562
- def headers(name = 'amq.match', opts = {}, &block)
563
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
564
- extended_opts = Exchange.add_default_options(:headers, name, opts, block)
565
-
566
- validate_parameters_match!(exchange, extended_opts)
567
-
568
- exchange
569
- else
570
- self.exchanges << Exchange.new(self, :headers, name, opts, &block)
571
- end
572
- end
573
-
574
- # Queues store and forward messages. Queues can be configured in the server
575
- # or created at runtime. Queues must be attached to at least one exchange
576
- # in order to receive messages from publishers.
577
- #
578
- # Like an Exchange, queue names starting with 'amq.' are reserved for
579
- # internal use. Attempts to create queue names in violation of this
580
- # reservation will raise MQ:Error (ACCESS_REFUSED).
581
- #
582
- # It is not supported to create a queue without a name; some string
583
- # (even the empty string) must be passed in the +name+ parameter.
584
- #
585
- # == Options
586
- # * :passive => true | false (default false)
587
- # If set, the server will not create the queue if it does not
588
- # already exist. The client can use this to check whether the queue
589
- # exists without modifying the server state.
590
- #
591
- # * :durable => true | false (default false)
592
- # If set when creating a new queue, the queue will be marked as
593
- # durable. Durable queues remain active when a server restarts.
594
- # Non-durable queues (transient queues) are purged if/when a
595
- # server restarts. Note that durable queues do not necessarily
596
- # hold persistent messages, although it does not make sense to
597
- # send persistent messages to a transient queue (though it is
598
- # allowed).
599
- #
600
- # Again, note the durability property on a queue has no influence on
601
- # the persistence of published messages. A durable queue containing
602
- # transient messages will flush those messages on a restart.
603
- #
604
- # If the queue has already been declared, any redeclaration will
605
- # ignore this setting. A queue may only be declared durable the
606
- # first time when it is created.
607
- #
608
- # * :exclusive => true | false (default false)
609
- # Exclusive queues may only be consumed from by the current connection.
610
- # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
611
- # single consumer is allowed to remove messages from this queue.
612
- #
613
- # The default is a shared queue. Multiple clients may consume messages
614
- # from this queue.
615
- #
616
- # Attempting to redeclare an already-declared queue as :exclusive => true
617
- # will raise MQ:Error.
618
- #
619
- # * :auto_delete = true | false (default false)
620
- # If set, the queue is deleted when all consumers have finished
621
- # using it. Last consumer can be cancelled either explicitly or because
622
- # its channel is closed. If there was no consumer ever on the queue, it
623
- # won't be deleted.
624
- #
625
- # The server waits for a short period of time before
626
- # determining the queue is unused to give time to the client code
627
- # to bind a queue to it.
628
- #
629
- # If the queue has been previously declared, this option is ignored
630
- # on subsequent declarations.
631
- #
632
- # Any remaining messages in the queue will be purged when the queue
633
- # is deleted regardless of the message's persistence setting.
634
- #
635
- # * :nowait => true | false (default true)
636
- # If set, the server will not respond to the method. The client should
637
- # not wait for a reply method. If the server could not complete the
638
- # method it will raise a channel or connection exception.
639
- #
640
- def queue(name, opts = {}, &block)
641
- if queue = self.queues.find { |queue| queue.name == name }
642
- extended_opts = Queue.add_default_options(name, opts, block)
643
-
644
- validate_parameters_match!(queue, extended_opts)
645
-
646
- queue
647
- else
648
- self.queues << Queue.new(self, name, opts, &block)
649
- end
650
- end
651
-
652
- def queue!(name, opts = {}, &block)
653
- self.queues.add! Queue.new(self, name, opts, &block)
654
- end
655
-
656
- # Takes a channel, queue and optional object.
657
- #
658
- # The optional object may be a class name, module name or object
659
- # instance. When given a class or module name, the object is instantiated
660
- # during this setup. The passed queue is automatically subscribed to so
661
- # it passes all messages (and their arguments) to the object.
662
- #
663
- # Marshalling and unmarshalling the objects is handled internally. This
664
- # marshalling is subject to the same restrictions as defined in the
665
- # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
666
- # library. See that documentation for further reference.
667
- #
668
- # When the optional object is not passed, the returned rpc reference is
669
- # used to send messages and arguments to the queue. See #method_missing
670
- # which does all of the heavy lifting with the proxy. Some client
671
- # elsewhere must call this method *with* the optional block so that
672
- # there is a valid destination. Failure to do so will just enqueue
673
- # marshalled messages that are never consumed.
674
- #
675
- # EM.run do
676
- # server = MQ.rpc('hash table node', Hash)
677
- #
678
- # client = MQ.rpc('hash table node')
679
- # client[:now] = Time.now
680
- # client[:one] = 1
681
- #
682
- # client.values do |res|
683
- # p 'client', :values => res
684
- # end
685
- #
686
- # client.keys do |res|
687
- # p 'client', :keys => res
688
- # EM.stop_event_loop
689
- # end
690
- # end
691
- #
692
- def rpc(name, obj = nil)
693
- rpcs[name] ||= RPC.new(self, name, obj)
694
- end
695
-
696
- def close(&block)
697
- @on_close = block
698
- if @deferred_status == :succeeded
699
- send Protocol::Channel::Close.new(:reply_code => 200,
700
- :reply_text => 'bye',
701
- :method_id => 0,
702
- :class_id => 0)
703
- else
704
- @closing = true
705
- end
706
- end
707
-
708
- # Define a message and callback block to be executed on all
709
- # errors.
710
- def self.error msg = nil, &blk
711
- if blk
712
- @error_callback = blk
713
- else
714
- @error_callback.call(msg) if @error_callback and msg
715
- end
716
- end
717
-
718
- def prefetch(size)
719
- @prefetch_size = size
720
-
721
- send Protocol::Basic::Qos.new(:prefetch_size => 0, :prefetch_count => size, :global => false)
722
-
723
- self
724
- end
725
-
726
- # Asks the broker to redeliver all unacknowledged messages on this
727
- # channel.
728
- #
729
- # * requeue (default false)
730
- # If this parameter is false, the message will be redelivered to the original recipient.
731
- # If this flag is true, the server will attempt to requeue the message, potentially then
732
- # delivering it to an alternative subscriber.
733
- #
734
- def recover(requeue = false)
735
- send Protocol::Basic::Recover.new(:requeue => requeue)
736
- self
737
- end
738
-
739
- # Returns a hash of all the exchange proxy objects.
740
- #
741
- # Not typically called by client code.
742
- def exchanges
743
- @exchanges ||= MQ::Collection.new
744
- end
745
-
746
- # Returns a hash of all the queue proxy objects.
747
- #
748
- # Not typically called by client code.
749
- def queues
750
- @queues ||= MQ::Collection.new
751
- end
752
-
753
- def get_queue
754
- if block_given?
755
- @get_queue_mutex.synchronize {
756
- yield( @get_queue ||= [] )
757
- }
758
- end
759
- end
760
-
761
- # Returns a hash of all rpc proxy objects.
762
- #
763
- # Not typically called by client code.
764
- def rpcs
765
- @rcps ||= {}
766
- end
767
-
768
- # Queue objects keyed on their consumer tags.
769
- #
770
- # Not typically called by client code.
771
- def consumers
772
- @consumers ||= {}
773
- end
774
-
775
- def reset
776
- @deferred_status = nil
777
- @channel = nil
778
- initialize @connection
779
-
780
- @consumers = {}
781
-
782
- exs = @exchanges
783
- @exchanges = MQ::Collection.new
784
- exs.each { |e| e.reset } if exs
785
-
786
- qus = @queues
787
- @queues = MQ::Collection.new
788
- qus.each { |q| q.reset } if qus
789
-
790
- prefetch(@prefetch_size) if @prefetch_size
791
- end
792
-
793
-
794
- #
795
- # Implementation
796
- #
797
-
798
- # May raise a MQ::Error exception when the frame payload contains a
799
- # Protocol::Channel::Close object.
800
- #
801
- # This usually occurs when a client attempts to perform an illegal
802
- # operation. A short, and incomplete, list of potential illegal operations
803
- # follows:
804
- # * publish a message to a deleted exchange (NOT_FOUND)
805
- # * declare an exchange using the reserved 'amq.' naming structure (ACCESS_REFUSED)
806
- #
807
- def process_frame(frame)
808
- log :received, frame
809
-
810
- case frame
811
- when Frame::Header
812
- @header = frame.payload
813
- @body = ''
814
- check_content_completion
815
-
816
- when Frame::Body
817
- @body << frame.payload
818
- check_content_completion
819
-
820
- when Frame::Method
821
- case method = frame.payload
822
- when Protocol::Channel::OpenOk
823
- send Protocol::Access::Request.new(:realm => '/data',
824
- :read => true,
825
- :write => true,
826
- :active => true,
827
- :passive => true)
828
-
829
- when Protocol::Access::RequestOk
830
- @ticket = method.ticket
831
- callback {
832
- send Protocol::Channel::Close.new(:reply_code => 200,
833
- :reply_text => 'bye',
834
- :method_id => 0,
835
- :class_id => 0)
836
- } if @closing
837
- succeed
838
-
839
- when Protocol::Basic::CancelOk
840
- if @consumer = consumers[ method.consumer_tag ]
841
- @consumer.cancelled
842
- else
843
- MQ.error "Basic.CancelOk for invalid consumer tag: #{method.consumer_tag}"
844
- end
845
-
846
- when Protocol::Exchange::DeclareOk
847
- # We can't use exchanges[method.exchange] because if the name would
848
- # be an empty string, then AMQP broker generated a random one.
849
- exchanges = self.exchanges.select { |exchange| exchange.opts[:nowait].eql?(false) }
850
- exchange = exchanges.reverse.find { |exchange| exchange.status.eql?(:unfinished) }
851
- exchange.receive_response method
852
-
853
- when Protocol::Queue::DeclareOk
854
- # We can't use queues[method.queue] because if the name would
855
- # be an empty string, then AMQP broker generated a random one.
856
- queues = self.queues.select { |queue| queue.opts[:nowait].eql?(false) }
857
- queue = queues.reverse.find { |queue| queue.status.eql?(:unfinished) }
858
- queue.receive_status method
859
-
860
- when Protocol::Queue::BindOk
861
- # We can't use queues[method.queue] because if the name would
862
- # be an empty string, then AMQP broker generated a random one.
863
- queues = self.queues.select { |queue| queue.sync_bind }
864
- queue = queues.reverse.find { |queue| queue.status.eql?(:unbound) }
865
- queue.after_bind method
866
-
867
- when Protocol::Basic::Deliver, Protocol::Basic::GetOk
868
- @method = method
869
- @header = nil
870
- @body = ''
871
-
872
- if method.is_a? Protocol::Basic::GetOk
873
- @consumer = get_queue { |q| q.shift }
874
- MQ.error "No pending Basic.GetOk requests" unless @consumer
875
- else
876
- @consumer = consumers[ method.consumer_tag ]
877
- MQ.error "Basic.Deliver for invalid consumer tag: #{method.consumer_tag}" unless @consumer
878
- end
879
-
880
- when Protocol::Basic::GetEmpty
881
- if @consumer = get_queue { |q| q.shift }
882
- @consumer.receive nil, nil
883
- else
884
- MQ.error "Basic.GetEmpty for invalid consumer"
885
- end
886
-
887
- when Protocol::Channel::Close
888
- @status = :closed
889
- MQ.error "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]} on #{@channel}"
890
-
891
- when Protocol::Channel::CloseOk
892
- @status = :closed
893
- @on_close && @on_close.call(self)
894
-
895
- @closing = false
896
- conn.callback { |c|
897
- c.channels.delete @channel
898
- c.close if c.channels.empty?
899
- }
900
-
901
- when Protocol::Basic::ConsumeOk
902
- if @consumer = consumers[ method.consumer_tag ]
903
- @consumer.confirm_subscribe
904
- else
905
- MQ.error "Basic.ConsumeOk for invalid consumer tag: #{method.consumer_tag}"
906
- end
907
- end
908
- end
909
- end # process_frame
910
-
911
-
912
- def send(*args)
913
- conn.callback { |c|
914
- @_send_mutex.synchronize do
915
- args.each do |data|
916
- unless self.closed?
917
- data.ticket = @ticket if @ticket and data.respond_to? :ticket=
918
- log :sending, data
919
- c.send data, :channel => @channel
920
- else
921
- unless data.class == AMQP::Protocol::Channel::CloseOk
922
- raise ChannelClosedError.new(self)
923
- end
924
- end
925
- end
926
- end
927
- }
928
- end # send
929
-
930
-
931
- def check_content_completion
932
- if @body.length >= @header.size
933
- @header.properties.update(@method.arguments)
934
- @consumer.receive @header, @body if @consumer
935
- @body = @header = @consumer = @method = nil
936
- end
937
- end # check_content_completion
938
-
939
-
940
- private
941
-
942
- def log(*args)
943
- return unless MQ.logging
944
- pp args
945
- puts
946
- end # log
947
-
948
- def validate_parameters_match!(entity, parameters)
949
- unless entity.opts == parameters || parameters[:passive]
950
- raise IncompatibleOptionsError.new(entity.name, entity.opts, parameters)
951
- end
952
- end # validate_parameters_match!(entity, parameters)
953
- end
954
-
955
- #-- convenience wrapper (read: HACK) for thread-local MQ object
956
-
957
- class MQ
958
- def MQ.default
959
- #-- XXX clear this when connection is closed
960
- Thread.current[:mq] ||= MQ.new
961
- end
962
-
963
- # Allows for calls to all MQ instance methods. This implicitly calls
964
- # MQ.new so that a new channel is allocated for subsequent operations.
965
- def MQ.method_missing meth, *args, &blk
966
- MQ.default.__send__(meth, *args, &blk)
967
- end
968
- end
969
-
970
- class MQ
971
- # unique identifier
972
- def MQ.id
973
- Thread.current[:mq_id] ||= "#{`hostname`.strip}-#{Process.pid}-#{Thread.current.object_id}"
974
- end
975
- end
31
+ require "amqp"