adamh-amqp 0.6.3.1

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