amqp 0.5.9 → 0.6.0

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.
data/lib/ext/em.rb CHANGED
@@ -5,8 +5,9 @@ rescue LoadError
5
5
  require 'eventmachine'
6
6
  end
7
7
 
8
- # copied from EM trunk, will be removed when 0.12.1 is released
9
- if EM::VERSION < '0.12.1'
8
+ #:stopdoc:
9
+
10
+ if EM::VERSION < '0.12.2'
10
11
 
11
12
  def EventMachine::run blk=nil, tail=nil, &block
12
13
  @tails ||= []
data/lib/ext/emfork.rb CHANGED
@@ -6,6 +6,8 @@ end
6
6
 
7
7
  require 'eventmachine'
8
8
 
9
+ #:stopdoc:
10
+
9
11
  # helper to fork off EM reactors
10
12
  def EM.fork num = 1, &blk
11
13
  unless @forks
data/lib/mq.rb CHANGED
@@ -1,8 +1,11 @@
1
+ #:main: README
2
+ #
3
+
1
4
  $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
2
5
  require 'amqp'
3
6
 
4
7
  class MQ
5
- %w[ exchange queue rpc ].each do |file|
8
+ %w[ exchange queue rpc header ].each do |file|
6
9
  require "mq/#{file}"
7
10
  end
8
11
 
@@ -11,13 +14,127 @@ class MQ
11
14
  attr_accessor :logging
12
15
  end
13
16
 
14
- class Error < Exception; end
17
+ # Raised whenever an illegal operation is attempted.
18
+ class Error < StandardError; end
15
19
  end
16
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
+ #
17
117
  class MQ
18
118
  include AMQP
19
119
  include EM::Deferrable
20
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
+ #
21
138
  def initialize connection = nil
22
139
  raise 'MQ can only be used from within EM.run{}' unless EM.reactor_running?
23
140
 
@@ -30,6 +147,15 @@ class MQ
30
147
  end
31
148
  attr_reader :channel
32
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
+ #
33
159
  def process_frame frame
34
160
  log :received, frame
35
161
 
@@ -42,7 +168,7 @@ class MQ
42
168
  @body << frame.payload
43
169
  if @body.length >= @header.size
44
170
  @header.properties.update(@method.arguments)
45
- @consumer.receive @header, @body
171
+ @consumer.receive @header, @body if @consumer
46
172
  @body = @header = @consumer = @method = nil
47
173
  end
48
174
 
@@ -52,7 +178,8 @@ class MQ
52
178
  send Protocol::Access::Request.new(:realm => '/data',
53
179
  :read => true,
54
180
  :write => true,
55
- :active => true)
181
+ :active => true,
182
+ :passive => true)
56
183
 
57
184
  when Protocol::Access::RequestOk
58
185
  @ticket = method.ticket
@@ -64,11 +191,32 @@ class MQ
64
191
  } if @closing
65
192
  succeed
66
193
 
67
- when Protocol::Basic::Deliver
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 ].recieve_status method
203
+
204
+ when Protocol::Basic::Deliver, Protocol::Basic::GetOk
68
205
  @method = method
69
206
  @header = nil
70
207
  @body = ''
71
- @consumer = queues[ method.consumer_tag ]
208
+
209
+ if method.is_a? Protocol::Basic::GetOk
210
+ @consumer = get_queue{|q| q.shift }
211
+ MQ.error "No pending Basic.GetOk requests" unless @consumer
212
+ else
213
+ @consumer = consumers[ method.consumer_tag ]
214
+ MQ.error "Basic.Deliver for invalid consumer tag: #{method.consumer_tag}" unless @consumer
215
+ end
216
+
217
+ when Protocol::Basic::GetEmpty
218
+ @consumer = get_queue{|q| q.shift }
219
+ @consumer.receive nil, nil
72
220
 
73
221
  when Protocol::Channel::Close
74
222
  raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]} on #{@channel}"
@@ -95,18 +243,387 @@ class MQ
95
243
  }
96
244
  end
97
245
 
98
- %w[ direct topic fanout ].each do |type|
99
- class_eval %[
100
- def #{type} name = 'amq.#{type}', opts = {}
101
- exchanges[name] ||= Exchange.new(self, :#{type}, name, opts)
102
- end
103
- ]
246
+ # Defines, intializes and returns an Exchange to act as an ingress
247
+ # point for all published messages.
248
+ #
249
+ # == Direct
250
+ # A direct exchange is useful for 1:1 communication between a publisher and
251
+ # subscriber. Messages are routed to the queue with a binding that shares
252
+ # the same name as the exchange. Alternately, the messages are routed to
253
+ # the bound queue that shares the same name as the routing key used for
254
+ # defining the exchange. This exchange type does not honor the +:key+ option
255
+ # when defining a new instance with a name. It _will_ honor the +:key+ option
256
+ # if the exchange name is the empty string.
257
+ # Allocating this exchange without a name _or_ with the empty string
258
+ # will use the internal 'amq.direct' exchange.
259
+ #
260
+ # Any published message, regardless of its persistence setting, is thrown
261
+ # away by the exchange when there are no queues bound to it.
262
+ #
263
+ # # exchange is named 'foo'
264
+ # exchange = MQ.direct('foo')
265
+ #
266
+ # # or, the exchange can use the default name (amq.direct) and perform
267
+ # # routing comparisons using the :key
268
+ # exchange = MQ.direct("", :key => 'foo')
269
+ # exchange.publish('some data') # will be delivered to queue bound to 'foo'
270
+ #
271
+ # queue = MQ.queue('foo')
272
+ # # can receive data since the queue name and the exchange key match exactly
273
+ # queue.pop { |data| puts "received data [#{data}]" }
274
+ #
275
+ # == Options
276
+ # * :passive => true | false (default false)
277
+ # If set, the server will not create the exchange if it does not
278
+ # already exist. The client can use this to check whether an exchange
279
+ # exists without modifying the server state.
280
+ #
281
+ # * :durable => true | false (default false)
282
+ # If set when creating a new exchange, the exchange will be marked as
283
+ # durable. Durable exchanges remain active when a server restarts.
284
+ # Non-durable exchanges (transient exchanges) are purged if/when a
285
+ # server restarts.
286
+ #
287
+ # A transient exchange (the default) is stored in memory-only. The
288
+ # exchange and all bindings will be lost on a server restart.
289
+ # It makes no sense to publish a persistent message to a transient
290
+ # exchange.
291
+ #
292
+ # Durable exchanges and their bindings are recreated upon a server
293
+ # restart. Any published messages not routed to a bound queue are lost.
294
+ #
295
+ # * :auto_delete => true | false (default false)
296
+ # If set, the exchange is deleted when all queues have finished
297
+ # using it. The server waits for a short period of time before
298
+ # determining the exchange is unused to give time to the client code
299
+ # to bind a queue to it.
300
+ #
301
+ # If the exchange has been previously declared, this option is ignored
302
+ # on subsequent declarations.
303
+ #
304
+ # * :internal => true | false (default false)
305
+ # If set, the exchange may not be used directly by publishers, but
306
+ # only when bound to other exchanges. Internal exchanges are used to
307
+ # construct wiring that is not visible to applications.
308
+ #
309
+ # * :nowait => true | false (default true)
310
+ # If set, the server will not respond to the method. The client should
311
+ # not wait for a reply method. If the server could not complete the
312
+ # method it will raise a channel or connection exception.
313
+ #
314
+ # == Exceptions
315
+ # Doing any of these activities are illegal and will raise MQ:Error.
316
+ # * redeclare an already-declared exchange to a different type
317
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
318
+ #
319
+ def direct name = 'amq.direct', opts = {}
320
+ exchanges[name] ||= Exchange.new(self, :direct, name, opts)
321
+ end
322
+
323
+ # Defines, intializes and returns an Exchange to act as an ingress
324
+ # point for all published messages.
325
+ #
326
+ # == Fanout
327
+ # A fanout exchange is useful for 1:N communication where one publisher
328
+ # feeds multiple subscribers. Like direct exchanges, messages published
329
+ # to a fanout exchange are delivered to queues whose name matches the
330
+ # exchange name (or are bound to that exchange name). Each queue gets
331
+ # its own copy of the message.
332
+ #
333
+ # Any published message, regardless of its persistence setting, is thrown
334
+ # away by the exchange when there are no queues bound to it.
335
+ #
336
+ # Like the direct exchange type, this exchange type does not honor the
337
+ # +:key+ option when defining a new instance with a name. It _will_ honor
338
+ # the +:key+ option if the exchange name is the empty string.
339
+ # Allocating this exchange without a name _or_ with the empty string
340
+ # will use the internal 'amq.fanout' exchange.
341
+ #
342
+ # EM.run do
343
+ # clock = MQ.fanout('clock')
344
+ # EM.add_periodic_timer(1) do
345
+ # puts "\npublishing #{time = Time.now}"
346
+ # clock.publish(Marshal.dump(time))
347
+ # end
348
+ #
349
+ # amq = MQ.queue('every second')
350
+ # amq.bind(MQ.fanout('clock')).subscribe do |time|
351
+ # puts "every second received #{Marshal.load(time)}"
352
+ # end
353
+ #
354
+ # # note the string passed to #bind
355
+ # MQ.queue('every 5 seconds').bind('clock').subscribe do |time|
356
+ # time = Marshal.load(time)
357
+ # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
358
+ # end
359
+ # end
360
+ #
361
+ # == Options
362
+ # * :passive => true | false (default false)
363
+ # If set, the server will not create the exchange if it does not
364
+ # already exist. The client can use this to check whether an exchange
365
+ # exists without modifying the server state.
366
+ #
367
+ # * :durable => true | false (default false)
368
+ # If set when creating a new exchange, the exchange will be marked as
369
+ # durable. Durable exchanges remain active when a server restarts.
370
+ # Non-durable exchanges (transient exchanges) are purged if/when a
371
+ # server restarts.
372
+ #
373
+ # A transient exchange (the default) is stored in memory-only. The
374
+ # exchange and all bindings will be lost on a server restart.
375
+ # It makes no sense to publish a persistent message to a transient
376
+ # exchange.
377
+ #
378
+ # Durable exchanges and their bindings are recreated upon a server
379
+ # restart. Any published messages not routed to a bound queue are lost.
380
+ #
381
+ # * :auto_delete => true | false (default false)
382
+ # If set, the exchange is deleted when all queues have finished
383
+ # using it. The server waits for a short period of time before
384
+ # determining the exchange is unused to give time to the client code
385
+ # to bind a queue to it.
386
+ #
387
+ # If the exchange has been previously declared, this option is ignored
388
+ # on subsequent declarations.
389
+ #
390
+ # * :internal => true | false (default false)
391
+ # If set, the exchange may not be used directly by publishers, but
392
+ # only when bound to other exchanges. Internal exchanges are used to
393
+ # construct wiring that is not visible to applications.
394
+ #
395
+ # * :nowait => true | false (default true)
396
+ # If set, the server will not respond to the method. The client should
397
+ # not wait for a reply method. If the server could not complete the
398
+ # method it will raise a channel or connection exception.
399
+ #
400
+ # == Exceptions
401
+ # Doing any of these activities are illegal and will raise MQ:Error.
402
+ # * redeclare an already-declared exchange to a different type
403
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
404
+ #
405
+ def fanout name = 'amq.fanout', opts = {}
406
+ exchanges[name] ||= Exchange.new(self, :fanout, name, opts)
104
407
  end
105
408
 
409
+ # Defines, intializes and returns an Exchange to act as an ingress
410
+ # point for all published messages.
411
+ #
412
+ # == Topic
413
+ # A topic exchange allows for messages to be published to an exchange
414
+ # tagged with a specific routing key. The Exchange uses the routing key
415
+ # to determine which queues to deliver the message. Wildcard matching
416
+ # is allowed. The topic must be declared using dot notation to separate
417
+ # each subtopic.
418
+ #
419
+ # This is the only exchange type to honor the +key+ hash key for all
420
+ # cases.
421
+ #
422
+ # Any published message, regardless of its persistence setting, is thrown
423
+ # away by the exchange when there are no queues bound to it.
424
+ #
425
+ # As part of the AMQP standard, each server _should_ predeclare a topic
426
+ # exchange called 'amq.topic' (this is not required by the standard).
427
+ # Allocating this exchange without a name _or_ with the empty string
428
+ # will use the internal 'amq.topic' exchange.
429
+ #
430
+ # The classic example is delivering market data. When publishing market
431
+ # data for stocks, we may subdivide the stream based on 2
432
+ # characteristics: nation code and trading symbol. The topic tree for
433
+ # Apple Computer would look like:
434
+ # 'stock.us.aapl'
435
+ # For a foreign stock, it may look like:
436
+ # 'stock.de.dax'
437
+ #
438
+ # When publishing data to the exchange, bound queues subscribing to the
439
+ # exchange indicate which data interests them by passing a routing key
440
+ # for matching against the published routing key.
441
+ #
442
+ # EM.run do
443
+ # exch = MQ.topic("stocks")
444
+ # keys = ['stock.us.aapl', 'stock.de.dax']
445
+ #
446
+ # EM.add_periodic_timer(1) do # every second
447
+ # puts
448
+ # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
449
+ # end
450
+ #
451
+ # # match against one dot-separated item
452
+ # MQ.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
453
+ # puts "us stock price [#{price}]"
454
+ # end
455
+ #
456
+ # # match against multiple dot-separated items
457
+ # MQ.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
458
+ # puts "all stocks: price [#{price}]"
459
+ # end
460
+ #
461
+ # # require exact match
462
+ # MQ.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
463
+ # puts "dax price [#{price}]"
464
+ # end
465
+ # end
466
+ #
467
+ # For matching, the '*' (asterisk) wildcard matches against one
468
+ # dot-separated item only. The '#' wildcard (hash or pound symbol)
469
+ # matches against 0 or more dot-separated items. If none of these
470
+ # symbols are used, the exchange performs a comparison looking for an
471
+ # exact match.
472
+ #
473
+ # == Options
474
+ # * :passive => true | false (default false)
475
+ # If set, the server will not create the exchange if it does not
476
+ # already exist. The client can use this to check whether an exchange
477
+ # exists without modifying the server state.
478
+ #
479
+ # * :durable => true | false (default false)
480
+ # If set when creating a new exchange, the exchange will be marked as
481
+ # durable. Durable exchanges remain active when a server restarts.
482
+ # Non-durable exchanges (transient exchanges) are purged if/when a
483
+ # server restarts.
484
+ #
485
+ # A transient exchange (the default) is stored in memory-only. The
486
+ # exchange and all bindings will be lost on a server restart.
487
+ # It makes no sense to publish a persistent message to a transient
488
+ # exchange.
489
+ #
490
+ # Durable exchanges and their bindings are recreated upon a server
491
+ # restart. Any published messages not routed to a bound queue are lost.
492
+ #
493
+ # * :auto_delete => true | false (default false)
494
+ # If set, the exchange is deleted when all queues have finished
495
+ # using it. The server waits for a short period of time before
496
+ # determining the exchange is unused to give time to the client code
497
+ # to bind a queue to it.
498
+ #
499
+ # If the exchange has been previously declared, this option is ignored
500
+ # on subsequent declarations.
501
+ #
502
+ # * :internal => true | false (default false)
503
+ # If set, the exchange may not be used directly by publishers, but
504
+ # only when bound to other exchanges. Internal exchanges are used to
505
+ # construct wiring that is not visible to applications.
506
+ #
507
+ # * :nowait => true | false (default true)
508
+ # If set, the server will not respond to the method. The client should
509
+ # not wait for a reply method. If the server could not complete the
510
+ # method it will raise a channel or connection exception.
511
+ #
512
+ # == Exceptions
513
+ # Doing any of these activities are illegal and will raise MQ:Error.
514
+ # * redeclare an already-declared exchange to a different type
515
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
516
+ #
517
+ def topic name = 'amq.topic', opts = {}
518
+ exchanges[name] ||= Exchange.new(self, :topic, name, opts)
519
+ end
520
+
521
+ # Queues store and forward messages. Queues can be configured in the server
522
+ # or created at runtime. Queues must be attached to at least one exchange
523
+ # in order to receive messages from publishers.
524
+ #
525
+ # Like an Exchange, queue names starting with 'amq.' are reserved for
526
+ # internal use. Attempts to create queue names in violation of this
527
+ # reservation will raise MQ:Error (ACCESS_REFUSED).
528
+ #
529
+ # It is not supported to create a queue without a name; some string
530
+ # (even the empty string) must be passed in the +name+ parameter.
531
+ #
532
+ # == Options
533
+ # * :passive => true | false (default false)
534
+ # If set, the server will not create the exchange if it does not
535
+ # already exist. The client can use this to check whether an exchange
536
+ # exists without modifying the server state.
537
+ #
538
+ # * :durable => true | false (default false)
539
+ # If set when creating a new queue, the queue will be marked as
540
+ # durable. Durable queues remain active when a server restarts.
541
+ # Non-durable queues (transient queues) are purged if/when a
542
+ # server restarts. Note that durable queues do not necessarily
543
+ # hold persistent messages, although it does not make sense to
544
+ # send persistent messages to a transient queue (though it is
545
+ # allowed).
546
+ #
547
+ # Again, note the durability property on a queue has no influence on
548
+ # the persistence of published messages. A durable queue containing
549
+ # transient messages will flush those messages on a restart.
550
+ #
551
+ # If the queue has already been declared, any redeclaration will
552
+ # ignore this setting. A queue may only be declared durable the
553
+ # first time when it is created.
554
+ #
555
+ # * :exclusive => true | false (default false)
556
+ # Exclusive queues may only be consumed from by the current connection.
557
+ # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
558
+ # single consumer is allowed to remove messages from this queue.
559
+ #
560
+ # The default is a shared queue. Multiple clients may consume messages
561
+ # from this queue.
562
+ #
563
+ # Attempting to redeclare an already-declared queue as :exclusive => true
564
+ # will raise MQ:Error.
565
+ #
566
+ # * :auto_delete = true | false (default false)
567
+ # If set, the queue is deleted when all consumers have finished
568
+ # using it. Last consumer can be cancelled either explicitly or because
569
+ # its channel is closed. If there was no consumer ever on the queue, it
570
+ # won't be deleted.
571
+ #
572
+ # The server waits for a short period of time before
573
+ # determining the queue is unused to give time to the client code
574
+ # to bind an exchange to it.
575
+ #
576
+ # If the queue has been previously declared, this option is ignored
577
+ # on subsequent declarations.
578
+ #
579
+ # Any remaining messages in the queue will be purged when the queue
580
+ # is deleted regardless of the message's persistence setting.
581
+ #
582
+ # * :nowait => true | false (default true)
583
+ # If set, the server will not respond to the method. The client should
584
+ # not wait for a reply method. If the server could not complete the
585
+ # method it will raise a channel or connection exception.
586
+ #
106
587
  def queue name, opts = {}
107
588
  queues[name] ||= Queue.new(self, name, opts)
108
589
  end
109
590
 
591
+ # Takes a channel, queue and optional object.
592
+ #
593
+ # The optional object may be a class name, module name or object
594
+ # instance. When given a class or module name, the object is instantiated
595
+ # during this setup. The passed queue is automatically subscribed to so
596
+ # it passes all messages (and their arguments) to the object.
597
+ #
598
+ # Marshalling and unmarshalling the objects is handled internally. This
599
+ # marshalling is subject to the same restrictions as defined in the
600
+ # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
601
+ # library. See that documentation for further reference.
602
+ #
603
+ # When the optional object is not passed, the returned rpc reference is
604
+ # used to send messages and arguments to the queue. See #method_missing
605
+ # which does all of the heavy lifting with the proxy. Some client
606
+ # elsewhere must call this method *with* the optional block so that
607
+ # there is a valid destination. Failure to do so will just enqueue
608
+ # marshalled messages that are never consumed.
609
+ #
610
+ # EM.run do
611
+ # server = MQ.rpc('hash table node', Hash)
612
+ #
613
+ # client = MQ.rpc('hash table node')
614
+ # client[:now] = Time.now
615
+ # client[:one] = 1
616
+ #
617
+ # client.values do |res|
618
+ # p 'client', :values => res
619
+ # end
620
+ #
621
+ # client.keys do |res|
622
+ # p 'client', :keys => res
623
+ # EM.stop_event_loop
624
+ # end
625
+ # end
626
+ #
110
627
  def rpc name, obj = nil
111
628
  rpcs[name] ||= RPC.new(self, name, obj)
112
629
  end
@@ -122,22 +639,70 @@ class MQ
122
639
  end
123
640
  end
124
641
 
125
- # keep track of proxy objects
126
-
642
+ # Define a message and callback block to be executed on all
643
+ # errors.
644
+ def self.error msg = nil, &blk
645
+ if blk
646
+ @error_callback = blk
647
+ else
648
+ @error_callback.call(msg) if @error_callback and msg
649
+ end
650
+ end
651
+
652
+ # Returns a hash of all the exchange proxy objects.
653
+ #
654
+ # Not typically called by client code.
127
655
  def exchanges
128
656
  @exchanges ||= {}
129
657
  end
130
658
 
659
+ # Returns a hash of all the queue proxy objects.
660
+ #
661
+ # Not typically called by client code.
131
662
  def queues
132
663
  @queues ||= {}
133
664
  end
134
665
 
666
+ def get_queue
667
+ if block_given?
668
+ (@get_queue_mutex ||= Mutex.new).synchronize{
669
+ yield( @get_queue ||= [] )
670
+ }
671
+ end
672
+ end
673
+
674
+ # Returns a hash of all rpc proxy objects.
675
+ #
676
+ # Not typically called by client code.
135
677
  def rpcs
136
678
  @rcps ||= {}
137
679
  end
138
680
 
681
+ # Queue objects keyed on their consumer tags.
682
+ #
683
+ # Not typically called by client code.
684
+ def consumers
685
+ @consumers ||= {}
686
+ end
687
+
688
+ def reset
689
+ @deferred_status = nil
690
+ @channel = nil
691
+ initialize @connection
692
+
693
+ @consumers = {}
694
+
695
+ exs = @exchanges
696
+ @exchanges = {}
697
+ exs.each{ |_,e| e.reset } if exs
698
+
699
+ qus = @queues
700
+ @queues = {}
701
+ qus.each{ |_,q| q.reset } if qus
702
+ end
703
+
139
704
  private
140
-
705
+
141
706
  def log *args
142
707
  return unless MQ.logging
143
708
  pp args
@@ -148,21 +713,23 @@ class MQ
148
713
  alias :conn :connection
149
714
  end
150
715
 
151
- # convenience wrapper (read: HACK) for thread-local MQ object
716
+ #-- convenience wrapper (read: HACK) for thread-local MQ object
152
717
 
153
718
  class MQ
154
719
  def MQ.default
155
- # XXX clear this when connection is closed
720
+ #-- XXX clear this when connection is closed
156
721
  Thread.current[:mq] ||= MQ.new
157
722
  end
158
723
 
724
+ # Allows for calls to all MQ instance methods. This implicitly calls
725
+ # MQ.new so that a new channel is allocated for subsequent operations.
159
726
  def MQ.method_missing meth, *args, &blk
160
727
  MQ.default.__send__(meth, *args, &blk)
161
728
  end
162
729
  end
163
730
 
164
- # unique identifier
165
731
  class MQ
732
+ # unique identifier
166
733
  def MQ.id
167
734
  Thread.current[:mq_id] ||= "#{`hostname`.strip}-#{Process.pid}-#{Thread.current.object_id}"
168
735
  end