arvicco-amqp 0.6.8 → 0.6.9

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