totty-amqp 0.6.7.1.totty

Sign up to get free protection for your applications and to get access to all the features.
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