amqp 0.7.0.pre → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +2 -0
  3. data/CHANGELOG +8 -2
  4. data/CONTRIBUTORS +22 -0
  5. data/Gemfile +3 -3
  6. data/README.md +20 -11
  7. data/Rakefile +30 -6
  8. data/amqp.gemspec +1 -1
  9. data/bin/cleanify.rb +50 -0
  10. data/examples/amqp/simple.rb +6 -4
  11. data/examples/mq/ack.rb +8 -6
  12. data/examples/mq/automatic_binding_for_default_direct_exchange.rb +65 -0
  13. data/examples/mq/callbacks.rb +9 -1
  14. data/examples/mq/clock.rb +17 -17
  15. data/examples/mq/hashtable.rb +19 -10
  16. data/examples/mq/internal.rb +13 -11
  17. data/examples/mq/logger.rb +38 -36
  18. data/examples/mq/multiclock.rb +16 -7
  19. data/examples/mq/pingpong.rb +16 -7
  20. data/examples/mq/pop.rb +8 -6
  21. data/examples/mq/primes-simple.rb +2 -0
  22. data/examples/mq/primes.rb +7 -5
  23. data/examples/mq/stocks.rb +14 -5
  24. data/lib/amqp.rb +12 -8
  25. data/lib/amqp/buffer.rb +35 -158
  26. data/lib/amqp/client.rb +34 -22
  27. data/lib/amqp/frame.rb +8 -64
  28. data/lib/amqp/protocol.rb +21 -70
  29. data/lib/amqp/server.rb +11 -9
  30. data/lib/amqp/spec.rb +8 -6
  31. data/lib/amqp/version.rb +2 -0
  32. data/lib/ext/blankslate.rb +3 -1
  33. data/lib/ext/em.rb +2 -0
  34. data/lib/ext/emfork.rb +13 -11
  35. data/lib/mq.rb +253 -156
  36. data/lib/mq/collection.rb +6 -88
  37. data/lib/mq/exchange.rb +70 -13
  38. data/lib/mq/header.rb +12 -6
  39. data/lib/mq/logger.rb +9 -7
  40. data/lib/mq/queue.rb +42 -30
  41. data/lib/mq/rpc.rb +6 -4
  42. data/protocol/codegen.rb +20 -18
  43. data/research/api.rb +10 -46
  44. data/research/primes-forked.rb +9 -7
  45. data/research/primes-processes.rb +74 -72
  46. data/research/primes-threaded.rb +9 -7
  47. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +61 -0
  48. data/spec/mq_helper.rb +70 -0
  49. data/spec/spec_helper.rb +84 -29
  50. data/spec/unit/amqp/buffer_spec.rb +178 -0
  51. data/spec/unit/amqp/client_spec.rb +472 -0
  52. data/spec/unit/amqp/frame_spec.rb +60 -0
  53. data/spec/unit/amqp/misc_spec.rb +123 -0
  54. data/spec/unit/amqp/protocol_spec.rb +53 -0
  55. data/spec/unit/mq/channel_close_spec.rb +15 -0
  56. data/spec/unit/mq/collection_spec.rb +129 -0
  57. data/spec/unit/mq/exchange_declaration_spec.rb +524 -0
  58. data/spec/unit/mq/misc_spec.rb +228 -0
  59. data/spec/unit/mq/mq_basic_spec.rb +39 -0
  60. data/spec/unit/mq/queue_declaration_spec.rb +97 -0
  61. data/spec/unit/mq/queue_spec.rb +71 -0
  62. metadata +33 -21
  63. data/Gemfile.lock +0 -16
  64. data/old/README +0 -30
  65. data/old/Rakefile +0 -12
  66. data/old/amqp-0.8.json +0 -606
  67. data/old/amqp_spec.rb +0 -796
  68. data/old/amqpc.rb +0 -695
  69. data/old/codegen.rb +0 -148
  70. data/spec/channel_close_spec.rb +0 -13
  71. data/spec/sync_async_spec.rb +0 -52
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class MQ
4
+ # MQ::Collection is used to store named AMQ model entities (exchanges, queues)
4
5
  class Collection < ::Array
5
6
  class IncompatibleItemError < ArgumentError
6
7
  def initialize(item)
@@ -21,15 +22,17 @@ class MQ
21
22
  # run declare twice in order to change options, because the AMQP
22
23
  # broker closes the connection if we try to do so).
23
24
 
24
- # Use Collection#<< for adding items to the collection.
25
+ # Use Collection# << for adding items to the collection.
25
26
  undef_method :[]=
26
27
 
27
28
  def <<(item)
28
- if (item.name rescue nil).nil? || ! self[item.name]
29
+ if (item.name rescue nil).nil? || !self[item.name]
29
30
  self.add!(item)
30
31
  end
31
32
 
32
- return item
33
+ # We can't just return the item, because in case the item isn't added
34
+ # to the collection, then it'd be different from self[item.name].
35
+ return self[item.name]
33
36
  end
34
37
 
35
38
  alias_method :__push__, :push
@@ -45,88 +48,3 @@ class MQ
45
48
  end
46
49
  end
47
50
  end
48
-
49
- if $0 =~ /bacon/ or $0 == __FILE__
50
- require "bacon"
51
-
52
- Item = Struct.new(:name)
53
-
54
- describe MQ::Collection do
55
- before do
56
- @items = 3.times.map { |int| Item.new("name-#{int}") }
57
- @collection = MQ::Collection.new(@items)
58
- end
59
-
60
- describe "accessors" do
61
- should "be accessible by its name" do
62
- @collection["name-1"].should.not.be.nil
63
- @collection["name-1"].should.eql(@items[1])
64
- end
65
-
66
- should "not allow to change already existing object" do
67
- lambda { @collection["name-1"] = Item.new("test") }.should.raise(NoMethodError)
68
- end
69
- end
70
-
71
- describe "#<<" do
72
- should "raise IncompatibleItemError if the argument doesn't have method :name" do
73
- lambda { @collection << nil }.should.raise(MQ::Collection::IncompatibleItemError)
74
- end
75
-
76
- should "add an item into the collection" do
77
- length = @collection.length
78
- @collection << Item.new("test")
79
- @collection.length.should.eql(length + 1)
80
- end
81
-
82
- should "not add an item to the collection if another item with given name already exists and the name IS NOT nil" do
83
- @collection << Item.new("test")
84
- length = @collection.length
85
- @collection << Item.new("test")
86
- @collection.length.should.eql(length)
87
- end
88
-
89
- should "add an item to the collection if another item with given name already exists and the name IS nil" do
90
- @collection << Item.new(nil)
91
- length = @collection.length
92
- @collection << Item.new(nil)
93
- @collection.length.should.eql(length + 1)
94
- end
95
-
96
- should "return the item" do
97
- item = Item.new("test")
98
- (@collection << item).should.eql item
99
- end
100
-
101
- should "return the item even if it already existed" do
102
- item = Item.new("test")
103
- @collection << item
104
- (@collection << item).should.eql item
105
- end
106
- end
107
-
108
- describe "#add!" do
109
- should "raise IncompatibleItemError if the argument doesn't have method :name" do
110
- lambda { @collection << nil }.should.raise(MQ::Collection::IncompatibleItemError)
111
- end
112
-
113
- should "add an item into the collection" do
114
- length = @collection.length
115
- @collection << Item.new("test")
116
- @collection.length.should.eql(length + 1)
117
- end
118
-
119
- should "add an item to the collection if another item with given name already exists" do
120
- @collection.add! Item.new("test")
121
- length = @collection.length
122
- @collection.add! Item.new("test")
123
- @collection.length.should.eql(length + 1)
124
- end
125
-
126
- should "return the item" do
127
- item = Item.new("test")
128
- (@collection << item).should.eql item
129
- end
130
- end
131
- end
132
- end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  # An Exchange acts as an ingress point for all published messages. An
3
5
  # exchange may also be described as a router or a matcher. Every
@@ -20,8 +22,41 @@ class MQ
20
22
  # the default exchange for publishing the messages.
21
23
  #
22
24
  class Exchange
25
+
26
+ #
27
+ # Behaviors
28
+ #
29
+
23
30
  include AMQP
24
31
 
32
+
33
+
34
+ #
35
+ # API
36
+ #
37
+
38
+
39
+ # The default exchange.
40
+ # Every queue is bind to this (direct) exchange by default.
41
+ # You can't remove it or bind there queue explicitly.
42
+
43
+ # Do NOT confuse with amq.direct: it's only a normal direct
44
+ # exchange and the only special thing about it is that it's
45
+ # predefined in the system, so you can use it straightaway.
46
+
47
+ # Example:
48
+ # MQ.new.queue("tasks")
49
+ # MQ::Exchange.default.publish("make clean", routing_key: "tasks")
50
+
51
+ # For more info see section 2.1.2.4 Automatic Mode of the AMQP 0.9.1 spec.
52
+ def self.default
53
+ @@default ||= self.new(MQ.new, :direct, "", :no_declare => true)
54
+ end
55
+
56
+ def self.add_default_options(type, name, opts, block)
57
+ { :exchange => name, :type => type, :nowait => block.nil? }.merge(opts)
58
+ end
59
+
25
60
  # Defines, intializes and returns an Exchange to act as an ingress
26
61
  # point for all published messages.
27
62
  #
@@ -194,14 +229,15 @@ class MQ
194
229
  # sensitive to the ordering of clients' actions!
195
230
  #
196
231
  # == Exceptions
197
- # Doing any of these activities are illegal and will raise MQ:Error.
198
- # * redeclare an already-declared exchange to a different type
232
+ # Doing any of these activities are illegal and will raise exceptions:
233
+ #
234
+ # * redeclare an already-declared exchange to a different type (raises MQ::IncompatibleOptionsError)
199
235
  # * :passive => true and the exchange does not exist (NOT_FOUND)
200
236
  #
201
- def initialize mq, type, name, opts = {}, &block
237
+ def initialize(mq, type, name, opts = {}, &block)
202
238
  @mq = mq
203
239
  @type, @opts = type, opts
204
- @opts = { :exchange => name, :type => type, :nowait => block.nil? }.merge(opts)
240
+ @opts = self.class.add_default_options(type, name, opts, block)
205
241
  @key = opts[:key]
206
242
  @name = name unless name.empty?
207
243
  @status = :unknown
@@ -225,7 +261,7 @@ class MQ
225
261
  # Call the callback immediately, as given exchange is already
226
262
  # declared.
227
263
  @status = :finished
228
- block.call(self)
264
+ block.call(self) if block
229
265
  end
230
266
 
231
267
  self.callback = block
@@ -274,8 +310,8 @@ class MQ
274
310
  # message stays in memory and is never persisted to non-volatile (slow)
275
311
  # storage.
276
312
  #
277
- def publish data, opts = {}
278
- @mq.callback{
313
+ def publish(data, opts = {})
314
+ @mq.callback {
279
315
  out = []
280
316
 
281
317
  out << Protocol::Basic::Publish.new({ :exchange => name,
@@ -317,8 +353,8 @@ class MQ
317
353
  # bindings. If the exchange has queue bindings the server does not
318
354
  # delete it but raises a channel exception instead (MQ:Error).
319
355
  #
320
- def delete opts = {}
321
- @mq.callback{
356
+ def delete(opts = {})
357
+ @mq.callback {
322
358
  @mq.send Protocol::Exchange::Delete.new({ :exchange => name,
323
359
  :nowait => true }.merge(opts))
324
360
  @mq.exchanges.delete name
@@ -326,13 +362,34 @@ class MQ
326
362
  nil
327
363
  end
328
364
 
365
+
366
+ def durable?
367
+ !!@opts[:durable]
368
+ end # durable?
369
+
370
+ def transient?
371
+ !self.durable?
372
+ end # transient?
373
+
374
+ def auto_deleted?
375
+ !!@opts[:auto_delete]
376
+ end # auto_deleted?
377
+ alias auto_deletable? auto_deleted?
378
+
379
+
329
380
  def reset
330
381
  @deferred_status = nil
331
382
  initialize @mq, @type, @name, @opts
332
383
  end
333
384
 
334
- def receive_response response
385
+
386
+
387
+ #
388
+ # Implementation
389
+ #
390
+
391
+ def receive_response(response)
335
392
  self.callback && self.callback.call(self)
336
- end
337
- end
338
- end
393
+ end # receive_response
394
+ end # Exchange
395
+ end # MQ
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  class Header
3
5
  include AMQP
@@ -9,20 +11,24 @@ class MQ
9
11
 
10
12
  # Acknowledges the receipt of this message with the server.
11
13
  def ack
12
- @mq.callback{
14
+ @mq.callback {
13
15
  @mq.send Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag])
14
16
  }
15
17
  end
16
18
 
17
19
  # Reject this message (XXX currently unimplemented in rabbitmq)
18
20
  # * :requeue => true | false (default false)
19
- def reject opts = {}
20
- @mq.callback{
21
- @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
22
- }
21
+ def reject(opts = {})
22
+ if @mq.broker.server_properties[:product] == "RabbitMQ"
23
+ raise NotImplementedError.new("RabbitMQ doesn't implement the Basic.Reject method\nSee http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2009-February/002853.html")
24
+ else
25
+ @mq.callback {
26
+ @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
27
+ }
28
+ end
23
29
  end
24
30
 
25
- def method_missing meth, *args, &blk
31
+ def method_missing(meth, *args, &blk)
26
32
  @header.send meth, *args, &blk
27
33
  end
28
34
 
@@ -1,6 +1,8 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  class Logger
3
- def initialize *args, &block
5
+ def initialize(*args, &block)
4
6
  opts = args.pop if args.last.is_a? Hash
5
7
  opts ||= {}
6
8
 
@@ -13,7 +15,7 @@ class MQ
13
15
  attr_reader :prop
14
16
  alias :base :prop
15
17
 
16
- def log severity, *args
18
+ def log(severity, *args)
17
19
  opts = args.pop if args.last.is_a? Hash and args.size != 1
18
20
  opts ||= {}
19
21
  opts = @prop.clone.update(opts)
@@ -56,7 +58,7 @@ class MQ
56
58
  end
57
59
  alias :method_missing :log
58
60
 
59
- def print data = nil, &block
61
+ def print(data = nil, &block)
60
62
  if block
61
63
  @printer = block
62
64
  elsif data.is_a? Proc
@@ -68,7 +70,7 @@ class MQ
68
70
  end
69
71
  end
70
72
  alias :printer :print
71
-
73
+
72
74
  def self.printer &block
73
75
  @printer = block if block
74
76
  @printer
@@ -77,13 +79,13 @@ class MQ
77
79
  def self.disabled?
78
80
  !!@disabled
79
81
  end
80
-
82
+
81
83
  def self.enable
82
84
  @disabled = false
83
85
  end
84
-
86
+
85
87
  def self.disable
86
88
  @disabled = true
87
89
  end
88
90
  end
89
- end
91
+ end
@@ -1,7 +1,13 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  class Queue
3
5
  include AMQP
4
6
 
7
+ def self.add_default_options(name, opts, block)
8
+ { :queue => name, :nowait => block.nil? }.merge(opts)
9
+ end
10
+
5
11
  # Queues store and forward messages. Queues can be configured in the server
6
12
  # or created at runtime. Queues must be attached to at least one exchange
7
13
  # in order to receive messages from publishers.
@@ -15,8 +21,8 @@ class MQ
15
21
  #
16
22
  # == Options
17
23
  # * :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
24
+ # If set, the server will not create the queue if it does not
25
+ # already exist. The client can use this to check whether the queue
20
26
  # exists without modifying the server state.
21
27
  #
22
28
  # * :durable => true | false (default false)
@@ -51,7 +57,7 @@ class MQ
51
57
  #
52
58
  # The server waits for a short period of time before
53
59
  # determining the queue is unused to give time to the client code
54
- # to bind an exchange to it.
60
+ # to bind a queue to it.
55
61
  #
56
62
  # If the queue has been previously declared, this option is ignored
57
63
  # on subsequent declarations.
@@ -61,9 +67,9 @@ class MQ
61
67
  # not wait for a reply method. If the server could not complete the
62
68
  # method it will raise a channel or connection exception.
63
69
  #
64
- def initialize mq, name, opts = {}, &block
70
+ def initialize(mq, name, opts = {}, &block)
65
71
  @mq = mq
66
- @opts = { :queue => name, :nowait => block.nil? }.merge(opts)
72
+ @opts = self.class.add_default_options(name, opts, block)
67
73
  @bindings ||= {}
68
74
  @name = name unless name.empty?
69
75
  @status = @opts[:nowait] ? :unknown : :unfinished
@@ -74,7 +80,7 @@ class MQ
74
80
  self.callback = block
75
81
  end
76
82
 
77
- attr_reader :name
83
+ attr_reader :name, :sync_bind
78
84
  attr_accessor :opts, :callback, :bind_callback
79
85
 
80
86
  # This method binds a queue to an exchange. Until a queue is
@@ -110,12 +116,13 @@ class MQ
110
116
  # not wait for a reply method. If the server could not complete the
111
117
  # method it will raise a channel or connection exception.
112
118
  #
113
- def bind exchange, opts = {}, &block
119
+ def bind(exchange, opts = {}, &block)
114
120
  @status = :unbound
121
+ @sync_bind = ! opts[:nowait]
115
122
  exchange = exchange.respond_to?(:name) ? exchange.name : exchange
116
123
  @bindings[exchange] = opts
117
124
 
118
- @mq.callback{
125
+ @mq.callback {
119
126
  @mq.send Protocol::Queue::Bind.new({ :queue => name,
120
127
  :exchange => exchange,
121
128
  :routing_key => opts[:key],
@@ -139,11 +146,11 @@ class MQ
139
146
  # not wait for a reply method. If the server could not complete the
140
147
  # method it will raise a channel or connection exception.
141
148
  #
142
- def unbind exchange, opts = {}
149
+ def unbind(exchange, opts = {})
143
150
  exchange = exchange.respond_to?(:name) ? exchange.name : exchange
144
151
  @bindings.delete exchange
145
152
 
146
- @mq.callback{
153
+ @mq.callback {
147
154
  @mq.send Protocol::Queue::Unbind.new({ :queue => name,
148
155
  :exchange => exchange,
149
156
  :routing_key => opts[:key],
@@ -172,8 +179,8 @@ class MQ
172
179
  # not wait for a reply method. If the server could not complete the
173
180
  # method it will raise a channel or connection exception.
174
181
  #
175
- def delete opts = {}
176
- @mq.callback{
182
+ def delete(opts = {})
183
+ @mq.callback {
177
184
  @mq.send Protocol::Queue::Delete.new({ :queue => name,
178
185
  :nowait => true }.merge(opts))
179
186
  }
@@ -183,8 +190,8 @@ class MQ
183
190
 
184
191
  # Purge all messages from the queue.
185
192
  #
186
- def purge opts = {}
187
- @mq.callback{
193
+ def purge(opts = {})
194
+ @mq.callback {
188
195
  @mq.send Protocol::Queue::Purge.new({ :queue => name,
189
196
  :nowait => true }.merge(opts))
190
197
  }
@@ -245,14 +252,14 @@ class MQ
245
252
  # not wait for a reply method. If the server could not complete the
246
253
  # method it will raise a channel or connection exception.
247
254
  #
248
- def pop opts = {}, &blk
255
+ def pop(opts = {}, &blk)
249
256
  if blk
250
257
  @on_pop = blk
251
258
  @on_pop_opts = opts
252
259
  end
253
260
 
254
- @mq.callback{
255
- @mq.get_queue{ |q|
261
+ @mq.callback {
262
+ @mq.get_queue { |q|
256
263
  q.push(self)
257
264
  @mq.send Protocol::Basic::Get.new({ :queue => name,
258
265
  :consumer_tag => name,
@@ -318,7 +325,7 @@ class MQ
318
325
  # automatically set :nowait => false. This is required for the server
319
326
  # to send a confirmation.
320
327
  #
321
- def subscribe opts = {}, &blk
328
+ def subscribe(opts = {}, &blk)
322
329
  @consumer_tag = "#{name}-#{Kernel.rand(999_999_999_999)}"
323
330
  @mq.consumers[@consumer_tag] = self
324
331
 
@@ -328,7 +335,7 @@ class MQ
328
335
  @on_msg_opts = opts
329
336
  opts[:nowait] = false if (@on_confirm_subscribe = opts[:confirm])
330
337
 
331
- @mq.callback{
338
+ @mq.callback {
332
339
  @mq.send Protocol::Basic::Consume.new({ :queue => name,
333
340
  :consumer_tag => @consumer_tag,
334
341
  :no_ack => !opts[:ack],
@@ -358,15 +365,15 @@ class MQ
358
365
  # not wait for a reply method. If the server could not complete the
359
366
  # method it will raise a channel or connection exception.
360
367
  #
361
- def unsubscribe opts = {}, &blk
368
+ def unsubscribe(opts = {}, &blk)
362
369
  @on_cancel = blk
363
- @mq.callback{
370
+ @mq.callback {
364
371
  @mq.send Protocol::Basic::Cancel.new({ :consumer_tag => @consumer_tag }.merge(opts))
365
372
  }
366
373
  self
367
374
  end
368
375
 
369
- def publish data, opts = {}
376
+ def publish(data, opts = {})
370
377
  exchange.publish(data, opts)
371
378
  end
372
379
 
@@ -390,7 +397,7 @@ class MQ
390
397
  # See AMQP::Protocol::Header for the hash properties available from
391
398
  # the headers parameter. See #pop or #subscribe for a code example.
392
399
  #
393
- def receive headers, body
400
+ def receive(headers, body)
394
401
  headers = MQ::Header.new(@mq, headers) unless headers.nil?
395
402
 
396
403
  if cb = (@on_msg || @on_pop)
@@ -400,27 +407,32 @@ class MQ
400
407
 
401
408
  # Get the number of messages and consumers on a queue.
402
409
  #
403
- # MQ.queue('name').status{ |num_messages, num_consumers|
410
+ # MQ.queue('name').status { |num_messages, num_consumers|
404
411
  # puts num_messages
405
412
  # }
406
413
  #
407
- def status opts = {}, &blk
414
+ def status(opts = {}, &blk)
408
415
  return @status if opts.empty? && blk.nil?
409
416
 
410
417
  @on_status = blk
411
- @mq.callback{
418
+ @mq.callback {
412
419
  @mq.send Protocol::Queue::Declare.new({ :queue => name,
413
420
  :passive => true }.merge(opts))
414
421
  }
415
422
  self
416
423
  end
417
424
 
418
- def receive_status declare_ok
425
+ def receive_status(declare_ok)
419
426
  @name = declare_ok.queue
420
427
  @status = :finished
421
428
 
422
429
  if self.callback
423
- self.callback.call(self, declare_ok.message_count, declare_ok.consumer_count)
430
+ # compatibility for a common case when callback only takes one argument
431
+ if self.callback.arity == 1
432
+ self.callback.call(self)
433
+ else
434
+ self.callback.call(self, declare_ok.message_count, declare_ok.consumer_count)
435
+ end
424
436
  end
425
437
 
426
438
  if @on_status
@@ -430,7 +442,7 @@ class MQ
430
442
  end
431
443
  end
432
444
 
433
- def after_bind bind_ok
445
+ def after_bind(bind_ok)
434
446
  @status = :bound
435
447
  if self.bind_callback
436
448
  self.bind_callback.call(self)
@@ -456,7 +468,7 @@ class MQ
456
468
 
457
469
  binds = @bindings
458
470
  @bindings = {}
459
- binds.each{|ex,opts| bind(ex, opts) }
471
+ binds.each { |ex, opts| bind(ex, opts) }
460
472
 
461
473
  if blk = @on_msg
462
474
  @on_msg = nil