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