fotonauts-amqp 0.6.1

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