ivanvanderbyl-amqp 0.6.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +4 -0
  2. data/HISTORY +27 -0
  3. data/README.md +169 -0
  4. data/Rakefile +24 -0
  5. data/TODO +32 -0
  6. data/VERSION +1 -0
  7. data/doc/EXAMPLE_01_PINGPONG +2 -0
  8. data/doc/EXAMPLE_02_CLOCK +2 -0
  9. data/doc/EXAMPLE_03_STOCKS +2 -0
  10. data/doc/EXAMPLE_04_MULTICLOCK +2 -0
  11. data/doc/EXAMPLE_05_ACK +2 -0
  12. data/doc/EXAMPLE_05_POP +2 -0
  13. data/doc/EXAMPLE_06_HASHTABLE +2 -0
  14. data/lib/amqp.rb +110 -0
  15. data/lib/amqp/buffer.rb +270 -0
  16. data/lib/amqp/client.rb +225 -0
  17. data/lib/amqp/frame.rb +66 -0
  18. data/lib/amqp/protocol.rb +161 -0
  19. data/lib/amqp/server.rb +99 -0
  20. data/lib/amqp/spec.rb +832 -0
  21. data/lib/amqp/version.rb +6 -0
  22. data/lib/ext/blankslate.rb +7 -0
  23. data/lib/ext/em.rb +8 -0
  24. data/lib/ext/emfork.rb +69 -0
  25. data/lib/mq.rb +875 -0
  26. data/lib/mq/exchange.rb +351 -0
  27. data/lib/mq/header.rb +33 -0
  28. data/lib/mq/logger.rb +89 -0
  29. data/lib/mq/queue.rb +455 -0
  30. data/lib/mq/rpc.rb +100 -0
  31. data/old/README +30 -0
  32. data/old/Rakefile +12 -0
  33. data/old/amqp-0.8.json +606 -0
  34. data/old/amqp_spec.rb +796 -0
  35. data/old/amqpc.rb +695 -0
  36. data/old/codegen.rb +148 -0
  37. data/protocol/amqp-0.8.json +617 -0
  38. data/protocol/amqp-0.8.xml +3908 -0
  39. data/protocol/codegen.rb +173 -0
  40. data/protocol/doc.txt +281 -0
  41. data/research/api.rb +88 -0
  42. data/research/primes-forked.rb +63 -0
  43. data/research/primes-processes.rb +135 -0
  44. data/research/primes-threaded.rb +49 -0
  45. data/tasks/common.rake +18 -0
  46. data/tasks/doc.rake +14 -0
  47. data/tasks/gem.rake +40 -0
  48. data/tasks/git.rake +34 -0
  49. data/tasks/spec.rake +15 -0
  50. data/tasks/version.rake +71 -0
  51. metadata +158 -0
@@ -0,0 +1,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.clone
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) unless headers.nil?
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