ivanvanderbyl-amqp 0.6.13.1

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