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