ivanvanderbyl-amqp 0.6.13.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 (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