amqp 0.6.7 → 0.7.0.pre

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.
@@ -1,5 +1,7 @@
1
1
  require File.expand_path('../frame', __FILE__)
2
2
 
3
+ require 'uri'
4
+
3
5
  module AMQP
4
6
  class Error < StandardError; end
5
7
 
@@ -184,7 +186,15 @@ module AMQP
184
186
  EM.reconnect @settings[:host], @settings[:port], self
185
187
  end
186
188
 
187
- def self.connect opts = {}
189
+ def self.connect amqp_url_or_opts = nil
190
+ if amqp_url_or_opts.is_a?(String)
191
+ opts = parse_amqp_url(amqp_url_or_opts)
192
+ elsif amqp_url_or_opts.is_a?(Hash)
193
+ opts = amqp_url_or_opts
194
+ elsif amqp_url_or_opts.nil?
195
+ opts = Hash.new
196
+ end
197
+
188
198
  opts = AMQP.settings.merge(opts)
189
199
  EM.connect opts[:host], opts[:port], self, opts
190
200
  end
@@ -206,5 +216,19 @@ module AMQP
206
216
  pp args
207
217
  puts
208
218
  end
219
+
220
+ def self.parse_amqp_url(amqp_url)
221
+ uri = URI.parse(amqp_url)
222
+ raise("amqp:// uri required!") unless %w{amqp amqps}.include? uri.scheme
223
+ opts = {}
224
+ opts[:user] = URI.unescape(uri.user) if uri.user
225
+ opts[:pass] = URI.unescape(uri.password) if uri.password
226
+ opts[:vhost] = URI.unescape(uri.path) if uri.path
227
+ opts[:host] = uri.host if uri.host
228
+ opts[:port] = uri.port ? uri.port :
229
+ {"amqp"=>5672, "amqps"=>5671}[uri.scheme]
230
+ opts[:ssl] = uri.scheme == "amqps"
231
+ return opts
232
+ end
209
233
  end
210
234
  end
@@ -121,4 +121,4 @@ if $0 =~ /bacon/ or $0 == __FILE__
121
121
  copy.should == orig
122
122
  end
123
123
  end
124
- end
124
+ end
@@ -209,4 +209,4 @@ if $0 =~ /bacon/ or $0 == __FILE__
209
209
  Protocol::Header.new(orig.to_binary).should == orig
210
210
  end
211
211
  end
212
- end
212
+ end
@@ -96,4 +96,4 @@ if __FILE__ == $0
96
96
  EM.run{
97
97
  AMQP::Server.start
98
98
  }
99
- end
99
+ end
@@ -1,3 +1,3 @@
1
1
  module AMQP
2
- VERSION = '0.6.7'
2
+ VERSION = '0.7.0'
3
3
  end
data/lib/mq.rb CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
5
5
  require 'amqp'
6
+ require 'mq/collection'
6
7
 
7
8
  class MQ
8
9
  %w[ exchange queue rpc header ].each do |file|
@@ -32,7 +33,7 @@ end
32
33
  # One consumer prints messages every second while the second consumer prints
33
34
  # messages every 2 seconds. After 5 seconds has elapsed, the 1 second
34
35
  # consumer is deleted.
35
- #
36
+ #
36
37
  # Of interest is the relationship of EventMachine to the process. All MQ
37
38
  # operations must occur within the context of an EM.run block. We start
38
39
  # EventMachine in its own thread with an empty block; all subsequent calls
@@ -44,39 +45,39 @@ end
44
45
  #
45
46
  # require 'rubygems'
46
47
  # require 'mq'
47
- #
48
+ #
48
49
  # thr = Thread.new { EM.run }
49
- #
50
+ #
50
51
  # # turns on extreme logging
51
52
  # #AMQP.logging = true
52
- #
53
+ #
53
54
  # def log *args
54
55
  # p args
55
56
  # end
56
- #
57
+ #
57
58
  # def publisher
58
59
  # clock = MQ.fanout('clock')
59
60
  # EM.add_periodic_timer(1) do
60
61
  # puts
61
- #
62
+ #
62
63
  # log :publishing, time = Time.now
63
64
  # clock.publish(Marshal.dump(time))
64
65
  # end
65
66
  # end
66
- #
67
+ #
67
68
  # def one_second_consumer
68
69
  # MQ.queue('every second').bind(MQ.fanout('clock')).subscribe do |time|
69
70
  # log 'every second', :received, Marshal.load(time)
70
71
  # end
71
72
  # end
72
- #
73
+ #
73
74
  # def two_second_consumer
74
75
  # MQ.queue('every 2 seconds').bind('clock').subscribe do |time|
75
76
  # time = Marshal.load(time)
76
77
  # log 'every 2 seconds', :received, time if time.sec % 2 == 0
77
78
  # end
78
79
  # end
79
- #
80
+ #
80
81
  # def delete_one_second
81
82
  # EM.add_timer(5) do
82
83
  # # delete the 'every second' queue
@@ -84,33 +85,33 @@ end
84
85
  # MQ.queue('every second').delete
85
86
  # end
86
87
  # end
87
- #
88
+ #
88
89
  # publisher
89
90
  # one_second_consumer
90
91
  # two_second_consumer
91
92
  # delete_one_second
92
93
  # thr.join
93
- #
94
+ #
94
95
  # __END__
95
- #
96
+ #
96
97
  # [:publishing, Tue Jan 06 22:46:14 -0600 2009]
97
98
  # ["every second", :received, Tue Jan 06 22:46:14 -0600 2009]
98
99
  # ["every 2 seconds", :received, Tue Jan 06 22:46:14 -0600 2009]
99
- #
100
+ #
100
101
  # [:publishing, Tue Jan 06 22:46:16 -0600 2009]
101
102
  # ["every second", :received, Tue Jan 06 22:46:16 -0600 2009]
102
103
  # ["every 2 seconds", :received, Tue Jan 06 22:46:16 -0600 2009]
103
- #
104
+ #
104
105
  # [:publishing, Tue Jan 06 22:46:17 -0600 2009]
105
106
  # ["every second", :received, Tue Jan 06 22:46:17 -0600 2009]
106
- #
107
+ #
107
108
  # [:publishing, Tue Jan 06 22:46:18 -0600 2009]
108
109
  # ["every second", :received, Tue Jan 06 22:46:18 -0600 2009]
109
110
  # ["every 2 seconds", :received, Tue Jan 06 22:46:18 -0600 2009]
110
111
  # ["Deleting [every second] queue"]
111
- #
112
+ #
112
113
  # [:publishing, Tue Jan 06 22:46:19 -0600 2009]
113
- #
114
+ #
114
115
  # [:publishing, Tue Jan 06 22:46:20 -0600 2009]
115
116
  # ["every 2 seconds", :received, Tue Jan 06 22:46:20 -0600 2009]
116
117
  #
@@ -146,9 +147,17 @@ class MQ
146
147
  }
147
148
  end
148
149
  attr_reader :channel, :connection
149
-
150
+
151
+ def check_content_completion
152
+ if @body.length >= @header.size
153
+ @header.properties.update(@method.arguments)
154
+ @consumer.receive @header, @body if @consumer
155
+ @body = @header = @consumer = @method = nil
156
+ end
157
+ end
158
+
150
159
  # May raise a MQ::Error exception when the frame payload contains a
151
- # Protocol::Channel::Close object.
160
+ # Protocol::Channel::Close object.
152
161
  #
153
162
  # This usually occurs when a client attempts to perform an illegal
154
163
  # operation. A short, and incomplete, list of potential illegal operations
@@ -163,14 +172,11 @@ class MQ
163
172
  when Frame::Header
164
173
  @header = frame.payload
165
174
  @body = ''
175
+ check_content_completion
166
176
 
167
177
  when Frame::Body
168
178
  @body << frame.payload
169
- if @body.length >= @header.size
170
- @header.properties.update(@method.arguments)
171
- @consumer.receive @header, @body if @consumer
172
- @body = @header = @consumer = @method = nil
173
- end
179
+ check_content_completion
174
180
 
175
181
  when Frame::Method
176
182
  case method = frame.payload
@@ -198,8 +204,26 @@ class MQ
198
204
  MQ.error "Basic.CancelOk for invalid consumer tag: #{method.consumer_tag}"
199
205
  end
200
206
 
207
+ when Protocol::Exchange::DeclareOk
208
+ # We can't use exchanges[method.exchange] because if the name would
209
+ # be an empty string, then AMQP broker generated a random one.
210
+ exchanges = self.exchanges.select { |exchange| exchange.opts[:nowait].eql?(false) }
211
+ exchange = exchanges.reverse.find { |exchange| exchange.status.eql?(:unfinished) }
212
+ exchange.receive_response method
213
+
201
214
  when Protocol::Queue::DeclareOk
202
- queues[ method.queue ].receive_status method
215
+ # We can't use queues[method.queue] because if the name would
216
+ # be an empty string, then AMQP broker generated a random one.
217
+ queues = self.queues.select { |queue| queue.opts[:nowait].eql?(false) }
218
+ queue = queues.reverse.find { |queue| queue.status.eql?(:unfinished) }
219
+ queue.receive_status method
220
+
221
+ when Protocol::Queue::BindOk
222
+ # We can't use queues[method.queue] because if the name would
223
+ # be an empty string, then AMQP broker generated a random one.
224
+ queues = self.queues.select { |queue| queue.opts[:nowait].eql?(false) }
225
+ queue = queues.reverse.find { |queue| queue.status.eql?(:unbound) }
226
+ queue.after_bind method
203
227
 
204
228
  when Protocol::Basic::Deliver, Protocol::Basic::GetOk
205
229
  @method = method
@@ -225,6 +249,8 @@ class MQ
225
249
  raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]} on #{@channel}"
226
250
 
227
251
  when Protocol::Channel::CloseOk
252
+ @on_close && @on_close.call(self)
253
+
228
254
  @closing = false
229
255
  conn.callback{ |c|
230
256
  c.channels.delete @channel
@@ -259,8 +285,8 @@ class MQ
259
285
  # == Direct
260
286
  # A direct exchange is useful for 1:1 communication between a publisher and
261
287
  # subscriber. Messages are routed to the queue with a binding that shares
262
- # the same name as the exchange. Alternately, the messages are routed to
263
- # the bound queue that shares the same name as the routing key used for
288
+ # the same name as the exchange. Alternately, the messages are routed to
289
+ # the bound queue that shares the same name as the routing key used for
264
290
  # defining the exchange. This exchange type does not honor the +:key+ option
265
291
  # when defining a new instance with a name. It _will_ honor the +:key+ option
266
292
  # if the exchange name is the empty string.
@@ -287,19 +313,19 @@ class MQ
287
313
  # If set, the server will not create the exchange if it does not
288
314
  # already exist. The client can use this to check whether an exchange
289
315
  # exists without modifying the server state.
290
- #
316
+ #
291
317
  # * :durable => true | false (default false)
292
318
  # If set when creating a new exchange, the exchange will be marked as
293
319
  # durable. Durable exchanges remain active when a server restarts.
294
320
  # Non-durable exchanges (transient exchanges) are purged if/when a
295
- # server restarts.
321
+ # server restarts.
296
322
  #
297
323
  # A transient exchange (the default) is stored in memory-only. The
298
324
  # exchange and all bindings will be lost on a server restart.
299
325
  # It makes no sense to publish a persistent message to a transient
300
326
  # exchange.
301
327
  #
302
- # Durable exchanges and their bindings are recreated upon a server
328
+ # Durable exchanges and their bindings are recreated upon a server
303
329
  # restart. Any published messages not routed to a bound queue are lost.
304
330
  #
305
331
  # * :auto_delete => true | false (default false)
@@ -326,25 +352,25 @@ class MQ
326
352
  # * redeclare an already-declared exchange to a different type
327
353
  # * :passive => true and the exchange does not exist (NOT_FOUND)
328
354
  #
329
- def direct name = 'amq.direct', opts = {}
330
- exchanges[name] ||= Exchange.new(self, :direct, name, opts)
355
+ def direct name = 'amq.direct', opts = {}, &block
356
+ self.exchanges << Exchange.new(self, :direct, name, opts, &block)
331
357
  end
332
358
 
333
359
  # Defines, intializes and returns an Exchange to act as an ingress
334
360
  # point for all published messages.
335
361
  #
336
362
  # == Fanout
337
- # A fanout exchange is useful for 1:N communication where one publisher
338
- # feeds multiple subscribers. Like direct exchanges, messages published
339
- # to a fanout exchange are delivered to queues whose name matches the
340
- # exchange name (or are bound to that exchange name). Each queue gets
363
+ # A fanout exchange is useful for 1:N communication where one publisher
364
+ # feeds multiple subscribers. Like direct exchanges, messages published
365
+ # to a fanout exchange are delivered to queues whose name matches the
366
+ # exchange name (or are bound to that exchange name). Each queue gets
341
367
  # its own copy of the message.
342
368
  #
343
369
  # Any published message, regardless of its persistence setting, is thrown
344
370
  # away by the exchange when there are no queues bound to it.
345
371
  #
346
- # Like the direct exchange type, this exchange type does not honor the
347
- # +:key+ option when defining a new instance with a name. It _will_ honor
372
+ # Like the direct exchange type, this exchange type does not honor the
373
+ # +:key+ option when defining a new instance with a name. It _will_ honor
348
374
  # the +:key+ option if the exchange name is the empty string.
349
375
  # Allocating this exchange without a name _or_ with the empty string
350
376
  # will use the internal 'amq.fanout' exchange.
@@ -373,19 +399,19 @@ class MQ
373
399
  # If set, the server will not create the exchange if it does not
374
400
  # already exist. The client can use this to check whether an exchange
375
401
  # exists without modifying the server state.
376
- #
402
+ #
377
403
  # * :durable => true | false (default false)
378
404
  # If set when creating a new exchange, the exchange will be marked as
379
405
  # durable. Durable exchanges remain active when a server restarts.
380
406
  # Non-durable exchanges (transient exchanges) are purged if/when a
381
- # server restarts.
407
+ # server restarts.
382
408
  #
383
409
  # A transient exchange (the default) is stored in memory-only. The
384
410
  # exchange and all bindings will be lost on a server restart.
385
411
  # It makes no sense to publish a persistent message to a transient
386
412
  # exchange.
387
413
  #
388
- # Durable exchanges and their bindings are recreated upon a server
414
+ # Durable exchanges and their bindings are recreated upon a server
389
415
  # restart. Any published messages not routed to a bound queue are lost.
390
416
  #
391
417
  # * :auto_delete => true | false (default false)
@@ -412,18 +438,18 @@ class MQ
412
438
  # * redeclare an already-declared exchange to a different type
413
439
  # * :passive => true and the exchange does not exist (NOT_FOUND)
414
440
  #
415
- def fanout name = 'amq.fanout', opts = {}
416
- exchanges[name] ||= Exchange.new(self, :fanout, name, opts)
441
+ def fanout name = 'amq.fanout', opts = {}, &block
442
+ self.exchanges << Exchange.new(self, :fanout, name, opts, &block)
417
443
  end
418
444
 
419
445
  # Defines, intializes and returns an Exchange to act as an ingress
420
446
  # point for all published messages.
421
447
  #
422
448
  # == Topic
423
- # A topic exchange allows for messages to be published to an exchange
449
+ # A topic exchange allows for messages to be published to an exchange
424
450
  # tagged with a specific routing key. The Exchange uses the routing key
425
- # to determine which queues to deliver the message. Wildcard matching
426
- # is allowed. The topic must be declared using dot notation to separate
451
+ # to determine which queues to deliver the message. Wildcard matching
452
+ # is allowed. The topic must be declared using dot notation to separate
427
453
  # each subtopic.
428
454
  #
429
455
  # This is the only exchange type to honor the +key+ hash key for all
@@ -432,14 +458,14 @@ class MQ
432
458
  # Any published message, regardless of its persistence setting, is thrown
433
459
  # away by the exchange when there are no queues bound to it.
434
460
  #
435
- # As part of the AMQP standard, each server _should_ predeclare a topic
461
+ # As part of the AMQP standard, each server _should_ predeclare a topic
436
462
  # exchange called 'amq.topic' (this is not required by the standard).
437
463
  # Allocating this exchange without a name _or_ with the empty string
438
464
  # will use the internal 'amq.topic' exchange.
439
465
  #
440
466
  # The classic example is delivering market data. When publishing market
441
- # data for stocks, we may subdivide the stream based on 2
442
- # characteristics: nation code and trading symbol. The topic tree for
467
+ # data for stocks, we may subdivide the stream based on 2
468
+ # characteristics: nation code and trading symbol. The topic tree for
443
469
  # Apple Computer would look like:
444
470
  # 'stock.us.aapl'
445
471
  # For a foreign stock, it may look like:
@@ -474,10 +500,10 @@ class MQ
474
500
  # end
475
501
  # end
476
502
  #
477
- # For matching, the '*' (asterisk) wildcard matches against one
478
- # dot-separated item only. The '#' wildcard (hash or pound symbol)
479
- # matches against 0 or more dot-separated items. If none of these
480
- # symbols are used, the exchange performs a comparison looking for an
503
+ # For matching, the '*' (asterisk) wildcard matches against one
504
+ # dot-separated item only. The '#' wildcard (hash or pound symbol)
505
+ # matches against 0 or more dot-separated items. If none of these
506
+ # symbols are used, the exchange performs a comparison looking for an
481
507
  # exact match.
482
508
  #
483
509
  # == Options
@@ -485,19 +511,19 @@ class MQ
485
511
  # If set, the server will not create the exchange if it does not
486
512
  # already exist. The client can use this to check whether an exchange
487
513
  # exists without modifying the server state.
488
- #
514
+ #
489
515
  # * :durable => true | false (default false)
490
516
  # If set when creating a new exchange, the exchange will be marked as
491
517
  # durable. Durable exchanges remain active when a server restarts.
492
518
  # Non-durable exchanges (transient exchanges) are purged if/when a
493
- # server restarts.
519
+ # server restarts.
494
520
  #
495
521
  # A transient exchange (the default) is stored in memory-only. The
496
522
  # exchange and all bindings will be lost on a server restart.
497
523
  # It makes no sense to publish a persistent message to a transient
498
524
  # exchange.
499
525
  #
500
- # Durable exchanges and their bindings are recreated upon a server
526
+ # Durable exchanges and their bindings are recreated upon a server
501
527
  # restart. Any published messages not routed to a bound queue are lost.
502
528
  #
503
529
  # * :auto_delete => true | false (default false)
@@ -524,25 +550,25 @@ class MQ
524
550
  # * redeclare an already-declared exchange to a different type
525
551
  # * :passive => true and the exchange does not exist (NOT_FOUND)
526
552
  #
527
- def topic name = 'amq.topic', opts = {}
528
- exchanges[name] ||= Exchange.new(self, :topic, name, opts)
553
+ def topic name = 'amq.topic', opts = {}, &block
554
+ self.exchanges << Exchange.new(self, :topic, name, opts, &block)
529
555
  end
530
556
 
531
557
  # Defines, intializes and returns an Exchange to act as an ingress
532
558
  # point for all published messages.
533
559
  #
534
560
  # == Headers
535
- # A headers exchange allows for messages to be published to an exchange
561
+ # A headers exchange allows for messages to be published to an exchange
536
562
  #
537
563
  # Any published message, regardless of its persistence setting, is thrown
538
564
  # away by the exchange when there are no queues bound to it.
539
565
  #
540
- # As part of the AMQP standard, each server _should_ predeclare a headers
566
+ # As part of the AMQP standard, each server _should_ predeclare a headers
541
567
  # exchange called 'amq.match' (this is not required by the standard).
542
568
  # Allocating this exchange without a name _or_ with the empty string
543
569
  # will use the internal 'amq.match' exchange.
544
570
  #
545
- # TODO: The classic example is ...
571
+ # TODO: The classic example is ...
546
572
  #
547
573
  # When publishing data to the exchange, bound queues subscribing to the
548
574
  # exchange indicate which data interests them by passing arguments
@@ -551,8 +577,8 @@ class MQ
551
577
  # may be 'any' or 'all'. If unspecified (in RabbitMQ at least), it defaults
552
578
  # to "all".
553
579
  #
554
- # A value of 'all' for 'x-match' implies that all values must match (i.e.
555
- # it does an AND of the headers ), while a value of 'any' implies that
580
+ # A value of 'all' for 'x-match' implies that all values must match (i.e.
581
+ # it does an AND of the headers ), while a value of 'any' implies that
556
582
  # at least one should match (ie. it does an OR).
557
583
  #
558
584
  # TODO: document behavior when either the binding or the message is missing
@@ -565,19 +591,19 @@ class MQ
565
591
  # If set, the server will not create the exchange if it does not
566
592
  # already exist. The client can use this to check whether an exchange
567
593
  # exists without modifying the server state.
568
- #
594
+ #
569
595
  # * :durable => true | false (default false)
570
596
  # If set when creating a new exchange, the exchange will be marked as
571
597
  # durable. Durable exchanges remain active when a server restarts.
572
598
  # Non-durable exchanges (transient exchanges) are purged if/when a
573
- # server restarts.
599
+ # server restarts.
574
600
  #
575
601
  # A transient exchange (the default) is stored in memory-only. The
576
602
  # exchange and all bindings will be lost on a server restart.
577
603
  # It makes no sense to publish a persistent message to a transient
578
604
  # exchange.
579
605
  #
580
- # Durable exchanges and their bindings are recreated upon a server
606
+ # Durable exchanges and their bindings are recreated upon a server
581
607
  # restart. Any published messages not routed to a bound queue are lost.
582
608
  #
583
609
  # * :auto_delete => true | false (default false)
@@ -604,8 +630,8 @@ class MQ
604
630
  # * redeclare an already-declared exchange to a different type
605
631
  # * :passive => true and the exchange does not exist (NOT_FOUND)
606
632
  # * using a value other than "any" or "all" for "x-match"
607
- def headers name = 'amq.match', opts = {}
608
- exchanges[name] ||= Exchange.new(self, :headers, name, opts)
633
+ def headers name = 'amq.match', opts = {}, &block
634
+ self.exchanges << Exchange.new(self, :headers, name, opts, &block)
609
635
  end
610
636
 
611
637
  # Queues store and forward messages. Queues can be configured in the server
@@ -624,7 +650,7 @@ class MQ
624
650
  # If set, the server will not create the exchange if it does not
625
651
  # already exist. The client can use this to check whether an exchange
626
652
  # exists without modifying the server state.
627
- #
653
+ #
628
654
  # * :durable => true | false (default false)
629
655
  # If set when creating a new queue, the queue will be marked as
630
656
  # durable. Durable queues remain active when a server restarts.
@@ -657,7 +683,7 @@ class MQ
657
683
  # If set, the queue is deleted when all consumers have finished
658
684
  # using it. Last consumer can be cancelled either explicitly or because
659
685
  # its channel is closed. If there was no consumer ever on the queue, it
660
- # won't be deleted.
686
+ # won't be deleted.
661
687
  #
662
688
  # The server waits for a short period of time before
663
689
  # determining the queue is unused to give time to the client code
@@ -674,8 +700,12 @@ class MQ
674
700
  # not wait for a reply method. If the server could not complete the
675
701
  # method it will raise a channel or connection exception.
676
702
  #
677
- def queue name, opts = {}
678
- queues[name] ||= Queue.new(self, name, opts)
703
+ def queue name, opts = {}, &block
704
+ self.queues << Queue.new(self, name, opts, &block)
705
+ end
706
+
707
+ def queue! name, opts = {}, &block
708
+ self.queues.add! Queue.new(self, name, opts, &block)
679
709
  end
680
710
 
681
711
  # Takes a channel, queue and optional object.
@@ -687,14 +717,14 @@ class MQ
687
717
  #
688
718
  # Marshalling and unmarshalling the objects is handled internally. This
689
719
  # marshalling is subject to the same restrictions as defined in the
690
- # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
720
+ # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
691
721
  # library. See that documentation for further reference.
692
722
  #
693
- # When the optional object is not passed, the returned rpc reference is
694
- # used to send messages and arguments to the queue. See #method_missing
695
- # which does all of the heavy lifting with the proxy. Some client
696
- # elsewhere must call this method *with* the optional block so that
697
- # there is a valid destination. Failure to do so will just enqueue
723
+ # When the optional object is not passed, the returned rpc reference is
724
+ # used to send messages and arguments to the queue. See #method_missing
725
+ # which does all of the heavy lifting with the proxy. Some client
726
+ # elsewhere must call this method *with* the optional block so that
727
+ # there is a valid destination. Failure to do so will just enqueue
698
728
  # marshalled messages that are never consumed.
699
729
  #
700
730
  # EM.run do
@@ -718,7 +748,8 @@ class MQ
718
748
  rpcs[name] ||= RPC.new(self, name, obj)
719
749
  end
720
750
 
721
- def close
751
+ def close(&block)
752
+ @on_close = block
722
753
  if @deferred_status == :succeeded
723
754
  send Protocol::Channel::Close.new(:reply_code => 200,
724
755
  :reply_text => 'bye',
@@ -762,14 +793,14 @@ class MQ
762
793
  #
763
794
  # Not typically called by client code.
764
795
  def exchanges
765
- @exchanges ||= {}
796
+ @exchanges ||= MQ::Collection.new
766
797
  end
767
798
 
768
799
  # Returns a hash of all the queue proxy objects.
769
800
  #
770
801
  # Not typically called by client code.
771
802
  def queues
772
- @queues ||= {}
803
+ @queues ||= MQ::Collection.new
773
804
  end
774
805
 
775
806
  def get_queue
@@ -844,4 +875,4 @@ class MQ
844
875
  def MQ.id
845
876
  Thread.current[:mq_id] ||= "#{`hostname`.strip}-#{Process.pid}-#{Thread.current.object_id}"
846
877
  end
847
- end
878
+ end