totty-amqp 0.6.7.1.totty

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