totty-amqp 0.6.7.1.totty

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README +143 -0
  2. data/Rakefile +20 -0
  3. data/amqp.todo +32 -0
  4. data/doc/EXAMPLE_01_PINGPONG +2 -0
  5. data/doc/EXAMPLE_02_CLOCK +2 -0
  6. data/doc/EXAMPLE_03_STOCKS +2 -0
  7. data/doc/EXAMPLE_04_MULTICLOCK +2 -0
  8. data/doc/EXAMPLE_05_ACK +2 -0
  9. data/doc/EXAMPLE_05_POP +2 -0
  10. data/doc/EXAMPLE_06_HASHTABLE +2 -0
  11. data/examples/amqp/simple.rb +79 -0
  12. data/examples/mq/ack.rb +45 -0
  13. data/examples/mq/clock.rb +56 -0
  14. data/examples/mq/hashtable.rb +52 -0
  15. data/examples/mq/internal.rb +49 -0
  16. data/examples/mq/logger.rb +88 -0
  17. data/examples/mq/multiclock.rb +49 -0
  18. data/examples/mq/pingpong.rb +45 -0
  19. data/examples/mq/pop.rb +43 -0
  20. data/examples/mq/primes-simple.rb +19 -0
  21. data/examples/mq/primes.rb +99 -0
  22. data/examples/mq/stocks.rb +58 -0
  23. data/lib/amqp/buffer.rb +395 -0
  24. data/lib/amqp/client.rb +210 -0
  25. data/lib/amqp/frame.rb +124 -0
  26. data/lib/amqp/protocol.rb +212 -0
  27. data/lib/amqp/server.rb +99 -0
  28. data/lib/amqp/spec.rb +832 -0
  29. data/lib/amqp/version.rb +3 -0
  30. data/lib/amqp.rb +152 -0
  31. data/lib/ext/blankslate.rb +7 -0
  32. data/lib/ext/em.rb +8 -0
  33. data/lib/ext/emfork.rb +69 -0
  34. data/lib/mq/exchange.rb +314 -0
  35. data/lib/mq/header.rb +33 -0
  36. data/lib/mq/logger.rb +89 -0
  37. data/lib/mq/queue.rb +455 -0
  38. data/lib/mq/rpc.rb +100 -0
  39. data/lib/mq.rb +877 -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. data/totty-amqp.gemspec +87 -0
  55. metadata +142 -0
data/lib/mq/queue.rb ADDED
@@ -0,0 +1,455 @@
1
+ class MQ
2
+ class Queue
3
+ include AMQP
4
+
5
+ # Queues store and forward messages. Queues can be configured in the server
6
+ # or created at runtime. Queues must be attached to at least one exchange
7
+ # in order to receive messages from publishers.
8
+ #
9
+ # Like an Exchange, queue names starting with 'amq.' are reserved for
10
+ # internal use. Attempts to create queue names in violation of this
11
+ # reservation will raise MQ:Error (ACCESS_REFUSED).
12
+ #
13
+ # When a queue is created without a name, the server will generate a
14
+ # unique name internally (not currently supported in this library).
15
+ #
16
+ # == Options
17
+ # * :passive => true | false (default false)
18
+ # If set, the server will not create the exchange if it does not
19
+ # already exist. The client can use this to check whether an exchange
20
+ # exists without modifying the server state.
21
+ #
22
+ # * :durable => true | false (default false)
23
+ # If set when creating a new queue, the queue will be marked as
24
+ # durable. Durable queues remain active when a server restarts.
25
+ # Non-durable queues (transient queues) are purged if/when a
26
+ # server restarts. Note that durable queues do not necessarily
27
+ # hold persistent messages, although it does not make sense to
28
+ # send persistent messages to a transient queue (though it is
29
+ # allowed).
30
+ #
31
+ # If the queue has already been declared, any redeclaration will
32
+ # ignore this setting. A queue may only be declared durable the
33
+ # first time when it is created.
34
+ #
35
+ # * :exclusive => true | false (default false)
36
+ # Exclusive queues may only be consumed from by the current connection.
37
+ # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
38
+ # single consumer is allowed to remove messages from this queue.
39
+ #
40
+ # The default is a shared queue. Multiple clients may consume messages
41
+ # from this queue.
42
+ #
43
+ # Attempting to redeclare an already-declared queue as :exclusive => true
44
+ # will raise MQ:Error.
45
+ #
46
+ # * :auto_delete = true | false (default false)
47
+ # If set, the queue is deleted when all consumers have finished
48
+ # using it. Last consumer can be cancelled either explicitly or because
49
+ # its channel is closed. If there was no consumer ever on the queue, it
50
+ # won't be deleted.
51
+ #
52
+ # The server waits for a short period of time before
53
+ # determining the queue is unused to give time to the client code
54
+ # to bind an exchange to it.
55
+ #
56
+ # If the queue has been previously declared, this option is ignored
57
+ # on subsequent declarations.
58
+ #
59
+ # * :nowait => true | false (default true)
60
+ # If set, the server will not respond to the method. The client should
61
+ # not wait for a reply method. If the server could not complete the
62
+ # method it will raise a channel or connection exception.
63
+ #
64
+ def initialize mq, name, opts = {}
65
+ @mq = mq
66
+ @opts = opts
67
+ @bindings ||= {}
68
+ @mq.queues[@name = name] ||= self
69
+ @mq.callback{
70
+ @mq.send Protocol::Queue::Declare.new({ :queue => name,
71
+ :nowait => true }.merge(opts))
72
+ }
73
+ end
74
+ attr_reader :name
75
+
76
+ # This method binds a queue to an exchange. Until a queue is
77
+ # bound it will not receive any messages. In a classic messaging
78
+ # model, store-and-forward queues are bound to a dest exchange
79
+ # and subscription queues are bound to a dest_wild exchange.
80
+ #
81
+ # A valid exchange name (or reference) must be passed as the first
82
+ # parameter. Both of these are valid:
83
+ # exch = MQ.direct('foo exchange')
84
+ # queue = MQ.queue('bar queue')
85
+ # queue.bind('foo.exchange') # OR
86
+ # queue.bind(exch)
87
+ #
88
+ # It is not valid to call #bind without the +exchange+ parameter.
89
+ #
90
+ # It is unnecessary to call #bind when the exchange name and queue
91
+ # name match exactly (for +direct+ and +fanout+ exchanges only).
92
+ # There is an implicit bind which will deliver the messages from
93
+ # the exchange to the queue.
94
+ #
95
+ # == Options
96
+ # * :key => 'some string'
97
+ # Specifies the routing key for the binding. The routing key is
98
+ # used for routing messages depending on the exchange configuration.
99
+ # Not all exchanges use a routing key - refer to the specific
100
+ # exchange documentation. If the routing key is empty and the queue
101
+ # name is empty, the routing key will be the current queue for the
102
+ # channel, which is the last declared queue.
103
+ #
104
+ # * :nowait => true | false (default true)
105
+ # If set, the server will not respond to the method. The client should
106
+ # not wait for a reply method. If the server could not complete the
107
+ # method it will raise a channel or connection exception.
108
+ #
109
+ def bind exchange, opts = {}
110
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
111
+ @bindings[exchange] = opts
112
+
113
+ @mq.callback{
114
+ @mq.send Protocol::Queue::Bind.new({ :queue => name,
115
+ :exchange => exchange,
116
+ :routing_key => opts[:key],
117
+ :nowait => true }.merge(opts))
118
+ }
119
+ self
120
+ end
121
+
122
+ # Remove the binding between the queue and exchange. The queue will
123
+ # not receive any more messages until it is bound to another
124
+ # exchange.
125
+ #
126
+ # Due to the asynchronous nature of the protocol, it is possible for
127
+ # "in flight" messages to be received after this call completes.
128
+ # Those messages will be serviced by the last block used in a
129
+ # #subscribe or #pop call.
130
+ #
131
+ # * :nowait => true | false (default true)
132
+ # If set, the server will not respond to the method. The client should
133
+ # not wait for a reply method. If the server could not complete the
134
+ # method it will raise a channel or connection exception.
135
+ #
136
+ def unbind exchange, opts = {}
137
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
138
+ @bindings.delete exchange
139
+
140
+ @mq.callback{
141
+ @mq.send Protocol::Queue::Unbind.new({ :queue => name,
142
+ :exchange => exchange,
143
+ :routing_key => opts[:key],
144
+ :nowait => true }.merge(opts))
145
+ }
146
+ self
147
+ end
148
+
149
+ # This method deletes a queue. When a queue is deleted any pending
150
+ # messages are sent to a dead-letter queue if this is defined in the
151
+ # server configuration, and all consumers on the queue are cancelled.
152
+ #
153
+ # == Options
154
+ # * :if_unused => true | false (default false)
155
+ # If set, the server will only delete the queue if it has no
156
+ # consumers. If the queue has consumers the server does does not
157
+ # delete it but raises a channel exception instead.
158
+ #
159
+ # * :if_empty => true | false (default false)
160
+ # If set, the server will only delete the queue if it has no
161
+ # messages. If the queue is not empty the server raises a channel
162
+ # exception.
163
+ #
164
+ # * :nowait => true | false (default true)
165
+ # If set, the server will not respond to the method. The client should
166
+ # not wait for a reply method. If the server could not complete the
167
+ # method it will raise a channel or connection exception.
168
+ #
169
+ def delete opts = {}
170
+ @mq.callback{
171
+ @mq.send Protocol::Queue::Delete.new({ :queue => name,
172
+ :nowait => true }.merge(opts))
173
+ }
174
+ @mq.queues.delete @name
175
+ nil
176
+ end
177
+
178
+ # Purge all messages from the queue.
179
+ #
180
+ def purge opts = {}
181
+ @mq.callback{
182
+ @mq.send Protocol::Queue::Purge.new({ :queue => name,
183
+ :nowait => true }.merge(opts))
184
+ }
185
+ nil
186
+ end
187
+
188
+ # This method provides a direct access to the messages in a queue
189
+ # using a synchronous dialogue that is designed for specific types of
190
+ # application where synchronous functionality is more important than
191
+ # performance.
192
+ #
193
+ # The provided block is passed a single message each time pop is called.
194
+ #
195
+ # EM.run do
196
+ # exchange = MQ.direct("foo queue")
197
+ # EM.add_periodic_timer(1) do
198
+ # exchange.publish("random number #{rand(1000)}")
199
+ # end
200
+ #
201
+ # # note that #bind is never called; it is implicit because
202
+ # # the exchange and queue names match
203
+ # queue = MQ.queue('foo queue')
204
+ # queue.pop { |body| puts "received payload [#{body}]" }
205
+ #
206
+ # EM.add_periodic_timer(1) { queue.pop }
207
+ # end
208
+ #
209
+ # If the block takes 2 parameters, both the +header+ and the +body+ will
210
+ # be passed in for processing. The header object is defined by
211
+ # AMQP::Protocol::Header.
212
+ #
213
+ # EM.run do
214
+ # exchange = MQ.direct("foo queue")
215
+ # EM.add_periodic_timer(1) do
216
+ # exchange.publish("random number #{rand(1000)}")
217
+ # end
218
+ #
219
+ # queue = MQ.queue('foo queue')
220
+ # queue.pop do |header, body|
221
+ # p header
222
+ # puts "received payload [#{body}]"
223
+ # end
224
+ #
225
+ # EM.add_periodic_timer(1) { queue.pop }
226
+ # end
227
+ #
228
+ # == Options
229
+ # * :ack => true | false (default false)
230
+ # If this field is set to false the server does not expect acknowledgments
231
+ # for messages. That is, when a message is delivered to the client
232
+ # the server automatically and silently acknowledges it on behalf
233
+ # of the client. This functionality increases performance but at
234
+ # the cost of reliability. Messages can get lost if a client dies
235
+ # before it can deliver them to the application.
236
+ #
237
+ # * :nowait => true | false (default true)
238
+ # If set, the server will not respond to the method. The client should
239
+ # not wait for a reply method. If the server could not complete the
240
+ # method it will raise a channel or connection exception.
241
+ #
242
+ def pop opts = {}, &blk
243
+ if blk
244
+ @on_pop = blk
245
+ @on_pop_opts = opts
246
+ end
247
+
248
+ @mq.callback{
249
+ @mq.get_queue{ |q|
250
+ q.push(self)
251
+ @mq.send Protocol::Basic::Get.new({ :queue => name,
252
+ :consumer_tag => name,
253
+ :no_ack => !opts[:ack],
254
+ :nowait => true }.merge(opts))
255
+ }
256
+ }
257
+
258
+ self
259
+ end
260
+
261
+ # Subscribes to asynchronous message delivery.
262
+ #
263
+ # The provided block is passed a single message each time the
264
+ # exchange matches a message to this queue.
265
+ #
266
+ # EM.run do
267
+ # exchange = MQ.direct("foo queue")
268
+ # EM.add_periodic_timer(1) do
269
+ # exchange.publish("random number #{rand(1000)}")
270
+ # end
271
+ #
272
+ # queue = MQ.queue('foo queue')
273
+ # queue.subscribe { |body| puts "received payload [#{body}]" }
274
+ # end
275
+ #
276
+ # If the block takes 2 parameters, both the +header+ and the +body+ will
277
+ # be passed in for processing. The header object is defined by
278
+ # AMQP::Protocol::Header.
279
+ #
280
+ # EM.run do
281
+ # exchange = MQ.direct("foo queue")
282
+ # EM.add_periodic_timer(1) do
283
+ # exchange.publish("random number #{rand(1000)}")
284
+ # end
285
+ #
286
+ # # note that #bind is never called; it is implicit because
287
+ # # the exchange and queue names match
288
+ # queue = MQ.queue('foo queue')
289
+ # queue.subscribe do |header, body|
290
+ # p header
291
+ # puts "received payload [#{body}]"
292
+ # end
293
+ # end
294
+ #
295
+ # == Options
296
+ # * :ack => true | false (default false)
297
+ # If this field is set to false the server does not expect acknowledgments
298
+ # for messages. That is, when a message is delivered to the client
299
+ # the server automatically and silently acknowledges it on behalf
300
+ # of the client. This functionality increases performance but at
301
+ # the cost of reliability. Messages can get lost if a client dies
302
+ # before it can deliver them to the application.
303
+ #
304
+ # * :nowait => true | false (default true)
305
+ # If set, the server will not respond to the method. The client should
306
+ # not wait for a reply method. If the server could not complete the
307
+ # method it will raise a channel or connection exception.
308
+ #
309
+ # * :confirm => proc (default nil)
310
+ # If set, this proc will be called when the server confirms subscription
311
+ # to the queue with a ConsumeOk message. Setting this option will
312
+ # automatically set :nowait => false. This is required for the server
313
+ # to send a confirmation.
314
+ #
315
+ def subscribe opts = {}, &blk
316
+ @consumer_tag = "#{name}-#{Kernel.rand(999_999_999_999)}"
317
+ @mq.consumers[@consumer_tag] = self
318
+
319
+ raise Error, 'already subscribed to the queue' if subscribed?
320
+
321
+ @on_msg = blk
322
+ @on_msg_opts = opts
323
+ opts[:nowait] = false if (@on_confirm_subscribe = opts[:confirm])
324
+
325
+ @mq.callback{
326
+ @mq.send Protocol::Basic::Consume.new({ :queue => name,
327
+ :consumer_tag => @consumer_tag,
328
+ :no_ack => !opts[:ack],
329
+ :nowait => true }.merge(opts))
330
+ }
331
+ self
332
+ end
333
+
334
+ # Removes the subscription from the queue and cancels the consumer.
335
+ # New messages will not be received by the queue. This call is similar
336
+ # in result to calling #unbind.
337
+ #
338
+ # Due to the asynchronous nature of the protocol, it is possible for
339
+ # "in flight" messages to be received after this call completes.
340
+ # Those messages will be serviced by the last block used in a
341
+ # #subscribe or #pop call.
342
+ #
343
+ # Additionally, if the queue was created with _autodelete_ set to
344
+ # true, the server will delete the queue after its wait period
345
+ # has expired unless the queue is bound to an active exchange.
346
+ #
347
+ # The method accepts a block which will be executed when the
348
+ # unsubscription request is acknowledged as complete by the server.
349
+ #
350
+ # * :nowait => true | false (default true)
351
+ # If set, the server will not respond to the method. The client should
352
+ # not wait for a reply method. If the server could not complete the
353
+ # method it will raise a channel or connection exception.
354
+ #
355
+ def unsubscribe opts = {}, &blk
356
+ @on_cancel = blk
357
+ @mq.callback{
358
+ @mq.send Protocol::Basic::Cancel.new({ :consumer_tag => @consumer_tag }.merge(opts))
359
+ }
360
+ self
361
+ end
362
+
363
+ def publish data, opts = {}
364
+ exchange.publish(data, opts)
365
+ end
366
+
367
+ # Boolean check to see if the current queue has already been subscribed
368
+ # to an exchange.
369
+ #
370
+ # Attempts to #subscribe multiple times to any exchange will raise an
371
+ # Exception. Only a single block at a time can be associated with any
372
+ # one queue for processing incoming messages.
373
+ #
374
+ def subscribed?
375
+ !!@on_msg
376
+ end
377
+
378
+ # Passes the message to the block passed to pop or subscribe.
379
+ #
380
+ # Performs an arity check on the block's parameters. If arity == 1,
381
+ # pass only the message body. If arity != 1, pass the headers and
382
+ # the body to the block.
383
+ #
384
+ # See AMQP::Protocol::Header for the hash properties available from
385
+ # the headers parameter. See #pop or #subscribe for a code example.
386
+ #
387
+ def receive headers, body
388
+ headers = MQ::Header.new(@mq, headers)
389
+
390
+ if cb = (@on_msg || @on_pop)
391
+ cb.call *(cb.arity == 1 ? [body] : [headers, body])
392
+ end
393
+ end
394
+
395
+ # Get the number of messages and consumers on a queue.
396
+ #
397
+ # MQ.queue('name').status{ |num_messages, num_consumers|
398
+ # puts num_messages
399
+ # }
400
+ #
401
+ def status opts = {}, &blk
402
+ @on_status = blk
403
+ @mq.callback{
404
+ @mq.send Protocol::Queue::Declare.new({ :queue => name,
405
+ :passive => true }.merge(opts))
406
+ }
407
+ self
408
+ end
409
+
410
+ def receive_status declare_ok
411
+ if @on_status
412
+ m, c = declare_ok.message_count, declare_ok.consumer_count
413
+ @on_status.call *(@on_status.arity == 1 ? [m] : [m, c])
414
+ @on_status = nil
415
+ end
416
+ end
417
+
418
+ def confirm_subscribe
419
+ @on_confirm_subscribe.call if @on_confirm_subscribe
420
+ @on_confirm_subscribe = nil
421
+ end
422
+
423
+ def cancelled
424
+ @on_cancel.call if @on_cancel
425
+ @on_cancel = @on_msg = nil
426
+ @mq.consumers.delete @consumer_tag
427
+ @mq.queues.delete(@name) if @opts[:auto_delete]
428
+ @consumer_tag = nil
429
+ end
430
+
431
+ def reset
432
+ @deferred_status = nil
433
+ initialize @mq, @name, @opts
434
+
435
+ binds = @bindings
436
+ @bindings = {}
437
+ binds.each{|ex,opts| bind(ex, opts) }
438
+
439
+ if blk = @on_msg
440
+ @on_msg = nil
441
+ subscribe @on_msg_opts, &blk
442
+ end
443
+
444
+ if @on_pop
445
+ pop @on_pop_opts, &@on_pop
446
+ end
447
+ end
448
+
449
+ private
450
+
451
+ def exchange
452
+ @exchange ||= Exchange.new(@mq, :direct, '', :key => name)
453
+ end
454
+ end
455
+ end
data/lib/mq/rpc.rb ADDED
@@ -0,0 +1,100 @@
1
+ class MQ
2
+ # Basic RPC (remote procedure call) facility.
3
+ #
4
+ # Needs more detail and explanation.
5
+ #
6
+ # EM.run do
7
+ # server = MQ.rpc('hash table node', Hash)
8
+ #
9
+ # client = MQ.rpc('hash table node')
10
+ # client[:now] = Time.now
11
+ # client[:one] = 1
12
+ #
13
+ # client.values do |res|
14
+ # p 'client', :values => res
15
+ # end
16
+ #
17
+ # client.keys do |res|
18
+ # p 'client', :keys => res
19
+ # EM.stop_event_loop
20
+ # end
21
+ # end
22
+ #
23
+ class RPC < BlankSlate
24
+ # Takes a channel, queue and optional object.
25
+ #
26
+ # The optional object may be a class name, module name or object
27
+ # instance. When given a class or module name, the object is instantiated
28
+ # during this setup. The passed queue is automatically subscribed to so
29
+ # it passes all messages (and their arguments) to the object.
30
+ #
31
+ # Marshalling and unmarshalling the objects is handled internally. This
32
+ # marshalling is subject to the same restrictions as defined in the
33
+ # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
34
+ # library. See that documentation for further reference.
35
+ #
36
+ # When the optional object is not passed, the returned rpc reference is
37
+ # used to send messages and arguments to the queue. See #method_missing
38
+ # which does all of the heavy lifting with the proxy. Some client
39
+ # elsewhere must call this method *with* the optional block so that
40
+ # there is a valid destination. Failure to do so will just enqueue
41
+ # marshalled messages that are never consumed.
42
+ #
43
+ def initialize mq, queue, obj = nil
44
+ @mq = mq
45
+ @mq.rpcs[queue] ||= self
46
+
47
+ if obj
48
+ @obj = case obj
49
+ when ::Class
50
+ obj.new
51
+ when ::Module
52
+ (::Class.new do include(obj) end).new
53
+ else
54
+ obj
55
+ end
56
+
57
+ @mq.queue(queue).subscribe(:ack=>true){ |info, request|
58
+ method, *args = ::Marshal.load(request)
59
+ ret = @obj.__send__(method, *args)
60
+
61
+ info.ack
62
+
63
+ if info.reply_to
64
+ @mq.queue(info.reply_to).publish(::Marshal.dump(ret), :key => info.reply_to, :message_id => info.message_id)
65
+ end
66
+ }
67
+ else
68
+ @callbacks ||= {}
69
+ # XXX implement and use queue(nil)
70
+ @queue = @mq.queue(@name = "random identifier #{::Kernel.rand(999_999_999_999)}", :auto_delete => true).subscribe{|info, msg|
71
+ if blk = @callbacks.delete(info.message_id)
72
+ blk.call ::Marshal.load(msg)
73
+ end
74
+ }
75
+ @remote = @mq.queue(queue)
76
+ end
77
+ end
78
+
79
+ # Calling MQ.rpc(*args) returns a proxy object without any methods beyond
80
+ # those in Object. All calls to the proxy are handled by #method_missing which
81
+ # works to marshal and unmarshal all method calls and their arguments.
82
+ #
83
+ # EM.run do
84
+ # server = MQ.rpc('hash table node', Hash)
85
+ # client = MQ.rpc('hash table node')
86
+ #
87
+ # # calls #method_missing on #[] which marshals the method name and
88
+ # # arguments to publish them to the remote
89
+ # client[:now] = Time.now
90
+ # ....
91
+ # end
92
+ #
93
+ def method_missing meth, *args, &blk
94
+ # XXX use uuids instead
95
+ message_id = "random message id #{::Kernel.rand(999_999_999_999)}"
96
+ @callbacks[message_id] = blk if blk
97
+ @remote.publish(::Marshal.dump([meth, *args]), :reply_to => blk ? @name : nil, :message_id => message_id)
98
+ end
99
+ end
100
+ end