arvicco-amqp 0.6.8 → 0.6.9

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