tmm1-amqp 0.5.9 → 0.6.0

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.
@@ -0,0 +1,46 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+ require 'mq'
3
+ require 'pp'
4
+
5
+ # For ack to work appropriatly you must shutdown AMQP gracefully,
6
+ # otherwise all items in your queue will be returned
7
+ Signal.trap('INT') { AMQP.stop{ EM.stop } }
8
+ Signal.trap('TERM'){ AMQP.stop{ EM.stop } }
9
+
10
+ EM.run do
11
+ MQ.queue('awesome').publish('Totally rad 1')
12
+ MQ.queue('awesome').publish('Totally rad 2')
13
+ MQ.queue('awesome').publish('Totally rad 3')
14
+
15
+ i = 0
16
+
17
+ # Stopping after the second item was acked will keep the 3rd item in the queue
18
+ MQ.queue('awesome').subscribe(:ack => true) do |h,m|
19
+ if (i+=1) == 3
20
+ puts 'Shutting down...'
21
+ AMQP.stop{ EM.stop }
22
+ end
23
+
24
+ if AMQP.closing?
25
+ puts "#{m} (ignored, redelivered later)"
26
+ else
27
+ puts m
28
+ h.ack
29
+ end
30
+ end
31
+ end
32
+
33
+ __END__
34
+
35
+ Totally rad 1
36
+ Totally rad 2
37
+ Shutting down...
38
+ Totally rad 3 (ignored, redelivered later)
39
+
40
+ When restarted:
41
+
42
+ Totally rad 3
43
+ Totally rad 1
44
+ Shutting down...
45
+ Totally rad 2 (ignored, redelivered later)
46
+ Totally rad 3 (ignored, redelivered later)
@@ -0,0 +1,43 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+ require 'mq'
3
+ require 'pp'
4
+
5
+ Signal.trap('INT') { AMQP.stop{ EM.stop } }
6
+ Signal.trap('TERM'){ AMQP.stop{ EM.stop } }
7
+
8
+ AMQP.start do
9
+ queue = MQ.queue('awesome')
10
+
11
+ queue.publish('Totally rad 1')
12
+ queue.publish('Totally rad 2')
13
+ EM.add_timer(5){ queue.publish('Totally rad 3') }
14
+
15
+ queue.pop{ |msg|
16
+ unless msg
17
+ # queue was empty
18
+ p [Time.now, :queue_empty!]
19
+
20
+ # try again in 1 second
21
+ EM.add_timer(1){ queue.pop }
22
+ else
23
+ # process this message
24
+ p [Time.now, msg]
25
+
26
+ # get the next message in the queue
27
+ queue.pop
28
+ end
29
+ }
30
+ end
31
+
32
+ __END__
33
+
34
+ [Wed Oct 15 15:24:30 -0700 2008, "Totally rad 1"]
35
+ [Wed Oct 15 15:24:30 -0700 2008, "Totally rad 2"]
36
+ [Wed Oct 15 15:24:30 -0700 2008, :queue_empty!]
37
+ [Wed Oct 15 15:24:31 -0700 2008, :queue_empty!]
38
+ [Wed Oct 15 15:24:32 -0700 2008, :queue_empty!]
39
+ [Wed Oct 15 15:24:33 -0700 2008, :queue_empty!]
40
+ [Wed Oct 15 15:24:34 -0700 2008, :queue_empty!]
41
+ [Wed Oct 15 15:24:35 -0700 2008, "Totally rad 3"]
42
+ [Wed Oct 15 15:24:35 -0700 2008, :queue_empty!]
43
+ [Wed Oct 15 15:24:36 -0700 2008, :queue_empty!]
@@ -5,7 +5,7 @@ require 'pp'
5
5
  EM.run do
6
6
 
7
7
  # connect to the amqp server
8
- connection = AMQP.connect(:host => 'dev.rabbitmq.com', :logging => false)
8
+ connection = AMQP.connect(:host => 'localhost', :logging => false)
9
9
 
10
10
  # open a channel on the AMQP connection
11
11
  channel = MQ.new(connection)
data/lib/amqp.rb CHANGED
@@ -15,6 +15,7 @@ module AMQP
15
15
  @logging = false
16
16
  attr_accessor :logging
17
17
  attr_reader :conn, :closing
18
+ alias :closing? :closing
18
19
  alias :connection :conn
19
20
  end
20
21
 
@@ -41,6 +42,41 @@ module AMQP
41
42
  }
42
43
  end
43
44
 
45
+ # Must be called to startup the connection to the AMQP server.
46
+ #
47
+ # The method takes several arguments and an optional block.
48
+ #
49
+ # This takes any option that is also accepted by EventMachine::connect.
50
+ # Additionally, there are several AMQP-specific options.
51
+ #
52
+ # * :user => String (default 'guest')
53
+ # The username as defined by the AMQP server.
54
+ # * :pass => String (default 'guest')
55
+ # The password for the associated :user as defined by the AMQP server.
56
+ # * :vhost => String (default '/')
57
+ # The virtual host as defined by the AMQP server.
58
+ # * :timeout => Numeric (default nil)
59
+ # Measured in seconds.
60
+ # * :logging => true | false (default false)
61
+ # Toggle the extremely verbose logging of all protocol communications
62
+ # between the client and the server. Extremely useful for debugging.
63
+ #
64
+ # AMQP.start do
65
+ # # default is to connect to localhost:5672
66
+ #
67
+ # # define queues, exchanges and bindings here.
68
+ # # also define all subscriptions and/or publishers
69
+ # # here.
70
+ #
71
+ # # this block never exits unless EM.stop_event_loop
72
+ # # is called.
73
+ # end
74
+ #
75
+ # Most code will use the MQ api. Any calls to MQ.direct / MQ.fanout /
76
+ # MQ.topic / MQ.queue will implicitly call #start. In those cases,
77
+ # it is sufficient to put your code inside of an EventMachine.run
78
+ # block. See the code examples in MQ for details.
79
+ #
44
80
  def self.start *args, &blk
45
81
  EM.run{
46
82
  @conn ||= connect *args
@@ -54,13 +90,13 @@ module AMQP
54
90
  end
55
91
 
56
92
  def self.stop
57
- if @conn
58
- puts "Shutting down..."
93
+ if @conn and not @closing
59
94
  @closing = true
60
95
  @conn.close{
61
96
  yield if block_given?
62
97
  @conn = nil
98
+ @closing = false
63
99
  }
64
100
  end
65
101
  end
66
- end
102
+ end
data/lib/amqp/buffer.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  if [].map.respond_to? :with_index
2
- class Array
2
+ class Array #:nodoc:
3
3
  def enum_with_index
4
4
  each.with_index
5
5
  end
@@ -9,7 +9,7 @@ else
9
9
  end
10
10
 
11
11
  module AMQP
12
- class Buffer
12
+ class Buffer #:nodoc: all
13
13
  class Overflow < StandardError; end
14
14
  class InvalidType < StandardError; end
15
15
 
data/lib/amqp/client.rb CHANGED
@@ -36,7 +36,8 @@ module AMQP
36
36
  succeed(self)
37
37
 
38
38
  when Protocol::Connection::Close
39
- raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
39
+ # raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
40
+ STDERR.puts "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
40
41
 
41
42
  when Protocol::Connection::CloseOk
42
43
  @on_disconnect.call if @on_disconnect
@@ -61,7 +62,7 @@ module AMQP
61
62
  @settings = opts
62
63
  extend AMQP.client
63
64
 
64
- @on_disconnect = proc{ raise Error, "Could not connect to server #{opts[:host]}:#{opts[:port]}" }
65
+ @on_disconnect ||= proc{ raise Error, "Could not connect to server #{opts[:host]}:#{opts[:port]}" }
65
66
 
66
67
  timeout @settings[:timeout] if @settings[:timeout]
67
68
  errback{ @on_disconnect.call }
@@ -69,7 +70,11 @@ module AMQP
69
70
 
70
71
  def connection_completed
71
72
  log 'connected'
72
- @on_disconnect = proc{ raise Error, 'Disconnected from server' }
73
+ # @on_disconnect = proc{ raise Error, 'Disconnected from server' }
74
+ unless @closing
75
+ @on_disconnect = method(:reconnect)
76
+ @reconnecting = false
77
+ end
73
78
  @buf = Buffer.new
74
79
  send_data HEADER
75
80
  send_data [1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4')
@@ -115,13 +120,21 @@ module AMQP
115
120
  send_data data.to_s
116
121
  end
117
122
 
123
+ #:stopdoc:
118
124
  # def send_data data
119
125
  # log 'send_data', data
120
126
  # super
121
127
  # end
128
+ #:startdoc:
122
129
 
123
130
  def close &on_disconnect
124
- @on_disconnect = on_disconnect if on_disconnect
131
+ if on_disconnect
132
+ @closing = true
133
+ @on_disconnect = proc{
134
+ on_disconnect.call
135
+ @closing = false
136
+ }
137
+ end
125
138
 
126
139
  callback{ |c|
127
140
  if c.channels.any?
@@ -136,7 +149,29 @@ module AMQP
136
149
  end
137
150
  }
138
151
  end
139
-
152
+
153
+ def reconnect force = false
154
+ if @reconnecting and not force
155
+ # wait 1 second after first reconnect attempt, in between each subsequent attempt
156
+ EM.add_timer(1){ reconnect(true) }
157
+ return
158
+ end
159
+
160
+ unless @reconnecting
161
+ @deferred_status = nil
162
+ initialize(@settings)
163
+
164
+ mqs = @channels
165
+ @channels = {}
166
+ mqs.each{ |_,mq| mq.reset } if mqs
167
+
168
+ @reconnecting = true
169
+ end
170
+
171
+ log 'reconnecting'
172
+ EM.reconnect @settings[:host], @settings[:port], self
173
+ end
174
+
140
175
  def self.connect opts = {}
141
176
  opts = AMQP.settings.merge(opts)
142
177
  EM.connect opts[:host], opts[:port], self, opts
data/lib/amqp/frame.rb CHANGED
@@ -3,7 +3,7 @@ require 'amqp/buffer'
3
3
  require 'amqp/protocol'
4
4
 
5
5
  module AMQP
6
- class Frame
6
+ class Frame #:nodoc: all
7
7
  def initialize payload = nil, channel = 0
8
8
  @channel, @payload = channel, payload
9
9
  end
data/lib/amqp/protocol.rb CHANGED
@@ -3,6 +3,7 @@ require 'amqp/buffer'
3
3
 
4
4
  module AMQP
5
5
  module Protocol
6
+ #:stopdoc:
6
7
  class Class::Method
7
8
  def initialize *args
8
9
  opts = args.pop if args.last.is_a? Hash
@@ -64,6 +65,29 @@ module AMQP
64
65
  end
65
66
  end
66
67
 
68
+ #:startdoc:
69
+ #
70
+ # Contains a properties hash that holds some potentially interesting
71
+ # information.
72
+ # * :delivery_mode
73
+ # 1 equals transient.
74
+ # 2 equals persistent. Unconsumed persistent messages will survive
75
+ # a server restart when they are stored in a durable queue.
76
+ # * :redelivered
77
+ # True or False
78
+ # * :routing_key
79
+ # The routing string used for matching this message to this queue.
80
+ # * :priority
81
+ # An integer in the range of 0 to 9 inclusive.
82
+ # * :content_type
83
+ # Always "application/octet-stream" (byte stream)
84
+ # * :exchange
85
+ # The source exchange which published this message.
86
+ # * :message_count
87
+ # The number of unconsumed messages contained in the queue.
88
+ # * :delivery_tag
89
+ # A monotonically increasing integer. This number should not be trusted
90
+ # as a sequence number. There is no guarantee it won't get reset.
67
91
  class Header
68
92
  def initialize *args
69
93
  opts = args.pop if args.last.is_a? Hash
@@ -132,6 +156,7 @@ module AMQP
132
156
  class_id, method_id = buf.read(:short, :short)
133
157
  classes[class_id].methods[method_id].new(buf)
134
158
  end
159
+ #:stopdoc:
135
160
  end
136
161
  end
137
162
 
data/lib/amqp/spec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
- # this file was autogenerated on Fri Aug 01 15:08:30 -0700 2008
3
- # using amqp-0.8.json (mtime: Fri Aug 01 15:08:22 -0700 2008)
2
+ #:stopdoc:
3
+ # this file was autogenerated on Sat Jan 03 14:05:57 -0600 2009
4
+ # using amqp-0.8.json (mtime: Sat Jan 03 08:58:13 -0600 2009)
4
5
  #
5
6
  # DO NOT EDIT! (edit protocol/codegen.rb instead, and run `rake codegen`)
6
7
 
@@ -1,7 +1,7 @@
1
1
  unless defined?(BlankSlate)
2
2
  class BlankSlate < BasicObject; end if defined?(BasicObject)
3
3
 
4
- class BlankSlate
4
+ class BlankSlate #:nodoc:
5
5
  instance_methods.each { |m| undef_method m unless m =~ /^__/ }
6
6
  end
7
7
  end
data/lib/ext/em.rb CHANGED
@@ -5,6 +5,8 @@ rescue LoadError
5
5
  require 'eventmachine'
6
6
  end
7
7
 
8
+ #:stopdoc:
9
+
8
10
  if EM::VERSION < '0.12.2'
9
11
 
10
12
  def EventMachine::run blk=nil, tail=nil, &block
data/lib/ext/emfork.rb CHANGED
@@ -6,6 +6,8 @@ end
6
6
 
7
7
  require 'eventmachine'
8
8
 
9
+ #:stopdoc:
10
+
9
11
  # helper to fork off EM reactors
10
12
  def EM.fork num = 1, &blk
11
13
  unless @forks
data/lib/mq.rb CHANGED
@@ -1,8 +1,11 @@
1
+ #:main: README
2
+ #
3
+
1
4
  $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
2
5
  require 'amqp'
3
6
 
4
7
  class MQ
5
- %w[ exchange queue rpc ].each do |file|
8
+ %w[ exchange queue rpc header ].each do |file|
6
9
  require "mq/#{file}"
7
10
  end
8
11
 
@@ -11,13 +14,127 @@ class MQ
11
14
  attr_accessor :logging
12
15
  end
13
16
 
17
+ # Raised whenever an illegal operation is attempted.
14
18
  class Error < StandardError; end
15
19
  end
16
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
+ #
17
117
  class MQ
18
118
  include AMQP
19
119
  include EM::Deferrable
20
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
+ #
21
138
  def initialize connection = nil
22
139
  raise 'MQ can only be used from within EM.run{}' unless EM.reactor_running?
23
140
 
@@ -30,6 +147,15 @@ class MQ
30
147
  end
31
148
  attr_reader :channel
32
149
 
150
+ # May raise a MQ::Error exception when the frame payload contains a
151
+ # Protocol::Channel::Close object.
152
+ #
153
+ # This usually occurs when a client attempts to perform an illegal
154
+ # operation. A short, and incomplete, list of potential illegal operations
155
+ # follows:
156
+ # * publish a message to a deleted exchange (NOT_FOUND)
157
+ # * declare an exchange using the reserved 'amq.' naming structure (ACCESS_REFUSED)
158
+ #
33
159
  def process_frame frame
34
160
  log :received, frame
35
161
 
@@ -117,18 +243,387 @@ class MQ
117
243
  }
118
244
  end
119
245
 
120
- %w[ direct topic fanout ].each do |type|
121
- class_eval %[
122
- def #{type} name = 'amq.#{type}', opts = {}
123
- exchanges[name] ||= Exchange.new(self, :#{type}, name, opts)
124
- end
125
- ]
246
+ # Defines, intializes and returns an Exchange to act as an ingress
247
+ # point for all published messages.
248
+ #
249
+ # == Direct
250
+ # A direct exchange is useful for 1:1 communication between a publisher and
251
+ # subscriber. Messages are routed to the queue with a binding that shares
252
+ # the same name as the exchange. Alternately, the messages are routed to
253
+ # the bound queue that shares the same name as the routing key used for
254
+ # defining the exchange. This exchange type does not honor the +:key+ option
255
+ # when defining a new instance with a name. It _will_ honor the +:key+ option
256
+ # if the exchange name is the empty string.
257
+ # Allocating this exchange without a name _or_ with the empty string
258
+ # will use the internal 'amq.direct' exchange.
259
+ #
260
+ # Any published message, regardless of its persistence setting, is thrown
261
+ # away by the exchange when there are no queues bound to it.
262
+ #
263
+ # # exchange is named 'foo'
264
+ # exchange = MQ.direct('foo')
265
+ #
266
+ # # or, the exchange can use the default name (amq.direct) and perform
267
+ # # routing comparisons using the :key
268
+ # exchange = MQ.direct("", :key => 'foo')
269
+ # exchange.publish('some data') # will be delivered to queue bound to 'foo'
270
+ #
271
+ # queue = MQ.queue('foo')
272
+ # # can receive data since the queue name and the exchange key match exactly
273
+ # queue.pop { |data| puts "received data [#{data}]" }
274
+ #
275
+ # == Options
276
+ # * :passive => true | false (default false)
277
+ # If set, the server will not create the exchange if it does not
278
+ # already exist. The client can use this to check whether an exchange
279
+ # exists without modifying the server state.
280
+ #
281
+ # * :durable => true | false (default false)
282
+ # If set when creating a new exchange, the exchange will be marked as
283
+ # durable. Durable exchanges remain active when a server restarts.
284
+ # Non-durable exchanges (transient exchanges) are purged if/when a
285
+ # server restarts.
286
+ #
287
+ # A transient exchange (the default) is stored in memory-only. The
288
+ # exchange and all bindings will be lost on a server restart.
289
+ # It makes no sense to publish a persistent message to a transient
290
+ # exchange.
291
+ #
292
+ # Durable exchanges and their bindings are recreated upon a server
293
+ # restart. Any published messages not routed to a bound queue are lost.
294
+ #
295
+ # * :auto_delete => true | false (default false)
296
+ # If set, the exchange is deleted when all queues have finished
297
+ # using it. The server waits for a short period of time before
298
+ # determining the exchange is unused to give time to the client code
299
+ # to bind a queue to it.
300
+ #
301
+ # If the exchange has been previously declared, this option is ignored
302
+ # on subsequent declarations.
303
+ #
304
+ # * :internal => true | false (default false)
305
+ # If set, the exchange may not be used directly by publishers, but
306
+ # only when bound to other exchanges. Internal exchanges are used to
307
+ # construct wiring that is not visible to applications.
308
+ #
309
+ # * :nowait => true | false (default true)
310
+ # If set, the server will not respond to the method. The client should
311
+ # not wait for a reply method. If the server could not complete the
312
+ # method it will raise a channel or connection exception.
313
+ #
314
+ # == Exceptions
315
+ # Doing any of these activities are illegal and will raise MQ:Error.
316
+ # * redeclare an already-declared exchange to a different type
317
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
318
+ #
319
+ def direct name = 'amq.direct', opts = {}
320
+ exchanges[name] ||= Exchange.new(self, :direct, name, opts)
321
+ end
322
+
323
+ # Defines, intializes and returns an Exchange to act as an ingress
324
+ # point for all published messages.
325
+ #
326
+ # == Fanout
327
+ # A fanout exchange is useful for 1:N communication where one publisher
328
+ # feeds multiple subscribers. Like direct exchanges, messages published
329
+ # to a fanout exchange are delivered to queues whose name matches the
330
+ # exchange name (or are bound to that exchange name). Each queue gets
331
+ # its own copy of the message.
332
+ #
333
+ # Any published message, regardless of its persistence setting, is thrown
334
+ # away by the exchange when there are no queues bound to it.
335
+ #
336
+ # Like the direct exchange type, this exchange type does not honor the
337
+ # +:key+ option when defining a new instance with a name. It _will_ honor
338
+ # the +:key+ option if the exchange name is the empty string.
339
+ # Allocating this exchange without a name _or_ with the empty string
340
+ # will use the internal 'amq.fanout' exchange.
341
+ #
342
+ # EM.run do
343
+ # clock = MQ.fanout('clock')
344
+ # EM.add_periodic_timer(1) do
345
+ # puts "\npublishing #{time = Time.now}"
346
+ # clock.publish(Marshal.dump(time))
347
+ # end
348
+ #
349
+ # amq = MQ.queue('every second')
350
+ # amq.bind(MQ.fanout('clock')).subscribe do |time|
351
+ # puts "every second received #{Marshal.load(time)}"
352
+ # end
353
+ #
354
+ # # note the string passed to #bind
355
+ # MQ.queue('every 5 seconds').bind('clock').subscribe do |time|
356
+ # time = Marshal.load(time)
357
+ # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
358
+ # end
359
+ # end
360
+ #
361
+ # == Options
362
+ # * :passive => true | false (default false)
363
+ # If set, the server will not create the exchange if it does not
364
+ # already exist. The client can use this to check whether an exchange
365
+ # exists without modifying the server state.
366
+ #
367
+ # * :durable => true | false (default false)
368
+ # If set when creating a new exchange, the exchange will be marked as
369
+ # durable. Durable exchanges remain active when a server restarts.
370
+ # Non-durable exchanges (transient exchanges) are purged if/when a
371
+ # server restarts.
372
+ #
373
+ # A transient exchange (the default) is stored in memory-only. The
374
+ # exchange and all bindings will be lost on a server restart.
375
+ # It makes no sense to publish a persistent message to a transient
376
+ # exchange.
377
+ #
378
+ # Durable exchanges and their bindings are recreated upon a server
379
+ # restart. Any published messages not routed to a bound queue are lost.
380
+ #
381
+ # * :auto_delete => true | false (default false)
382
+ # If set, the exchange is deleted when all queues have finished
383
+ # using it. The server waits for a short period of time before
384
+ # determining the exchange is unused to give time to the client code
385
+ # to bind a queue to it.
386
+ #
387
+ # If the exchange has been previously declared, this option is ignored
388
+ # on subsequent declarations.
389
+ #
390
+ # * :internal => true | false (default false)
391
+ # If set, the exchange may not be used directly by publishers, but
392
+ # only when bound to other exchanges. Internal exchanges are used to
393
+ # construct wiring that is not visible to applications.
394
+ #
395
+ # * :nowait => true | false (default true)
396
+ # If set, the server will not respond to the method. The client should
397
+ # not wait for a reply method. If the server could not complete the
398
+ # method it will raise a channel or connection exception.
399
+ #
400
+ # == Exceptions
401
+ # Doing any of these activities are illegal and will raise MQ:Error.
402
+ # * redeclare an already-declared exchange to a different type
403
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
404
+ #
405
+ def fanout name = 'amq.fanout', opts = {}
406
+ exchanges[name] ||= Exchange.new(self, :fanout, name, opts)
126
407
  end
127
408
 
409
+ # Defines, intializes and returns an Exchange to act as an ingress
410
+ # point for all published messages.
411
+ #
412
+ # == Topic
413
+ # A topic exchange allows for messages to be published to an exchange
414
+ # tagged with a specific routing key. The Exchange uses the routing key
415
+ # to determine which queues to deliver the message. Wildcard matching
416
+ # is allowed. The topic must be declared using dot notation to separate
417
+ # each subtopic.
418
+ #
419
+ # This is the only exchange type to honor the +key+ hash key for all
420
+ # cases.
421
+ #
422
+ # Any published message, regardless of its persistence setting, is thrown
423
+ # away by the exchange when there are no queues bound to it.
424
+ #
425
+ # As part of the AMQP standard, each server _should_ predeclare a topic
426
+ # exchange called 'amq.topic' (this is not required by the standard).
427
+ # Allocating this exchange without a name _or_ with the empty string
428
+ # will use the internal 'amq.topic' exchange.
429
+ #
430
+ # The classic example is delivering market data. When publishing market
431
+ # data for stocks, we may subdivide the stream based on 2
432
+ # characteristics: nation code and trading symbol. The topic tree for
433
+ # Apple Computer would look like:
434
+ # 'stock.us.aapl'
435
+ # For a foreign stock, it may look like:
436
+ # 'stock.de.dax'
437
+ #
438
+ # When publishing data to the exchange, bound queues subscribing to the
439
+ # exchange indicate which data interests them by passing a routing key
440
+ # for matching against the published routing key.
441
+ #
442
+ # EM.run do
443
+ # exch = MQ.topic("stocks")
444
+ # keys = ['stock.us.aapl', 'stock.de.dax']
445
+ #
446
+ # EM.add_periodic_timer(1) do # every second
447
+ # puts
448
+ # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
449
+ # end
450
+ #
451
+ # # match against one dot-separated item
452
+ # MQ.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
453
+ # puts "us stock price [#{price}]"
454
+ # end
455
+ #
456
+ # # match against multiple dot-separated items
457
+ # MQ.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
458
+ # puts "all stocks: price [#{price}]"
459
+ # end
460
+ #
461
+ # # require exact match
462
+ # MQ.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
463
+ # puts "dax price [#{price}]"
464
+ # end
465
+ # end
466
+ #
467
+ # For matching, the '*' (asterisk) wildcard matches against one
468
+ # dot-separated item only. The '#' wildcard (hash or pound symbol)
469
+ # matches against 0 or more dot-separated items. If none of these
470
+ # symbols are used, the exchange performs a comparison looking for an
471
+ # exact match.
472
+ #
473
+ # == Options
474
+ # * :passive => true | false (default false)
475
+ # If set, the server will not create the exchange if it does not
476
+ # already exist. The client can use this to check whether an exchange
477
+ # exists without modifying the server state.
478
+ #
479
+ # * :durable => true | false (default false)
480
+ # If set when creating a new exchange, the exchange will be marked as
481
+ # durable. Durable exchanges remain active when a server restarts.
482
+ # Non-durable exchanges (transient exchanges) are purged if/when a
483
+ # server restarts.
484
+ #
485
+ # A transient exchange (the default) is stored in memory-only. The
486
+ # exchange and all bindings will be lost on a server restart.
487
+ # It makes no sense to publish a persistent message to a transient
488
+ # exchange.
489
+ #
490
+ # Durable exchanges and their bindings are recreated upon a server
491
+ # restart. Any published messages not routed to a bound queue are lost.
492
+ #
493
+ # * :auto_delete => true | false (default false)
494
+ # If set, the exchange is deleted when all queues have finished
495
+ # using it. The server waits for a short period of time before
496
+ # determining the exchange is unused to give time to the client code
497
+ # to bind a queue to it.
498
+ #
499
+ # If the exchange has been previously declared, this option is ignored
500
+ # on subsequent declarations.
501
+ #
502
+ # * :internal => true | false (default false)
503
+ # If set, the exchange may not be used directly by publishers, but
504
+ # only when bound to other exchanges. Internal exchanges are used to
505
+ # construct wiring that is not visible to applications.
506
+ #
507
+ # * :nowait => true | false (default true)
508
+ # If set, the server will not respond to the method. The client should
509
+ # not wait for a reply method. If the server could not complete the
510
+ # method it will raise a channel or connection exception.
511
+ #
512
+ # == Exceptions
513
+ # Doing any of these activities are illegal and will raise MQ:Error.
514
+ # * redeclare an already-declared exchange to a different type
515
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
516
+ #
517
+ def topic name = 'amq.topic', opts = {}
518
+ exchanges[name] ||= Exchange.new(self, :topic, name, opts)
519
+ end
520
+
521
+ # Queues store and forward messages. Queues can be configured in the server
522
+ # or created at runtime. Queues must be attached to at least one exchange
523
+ # in order to receive messages from publishers.
524
+ #
525
+ # Like an Exchange, queue names starting with 'amq.' are reserved for
526
+ # internal use. Attempts to create queue names in violation of this
527
+ # reservation will raise MQ:Error (ACCESS_REFUSED).
528
+ #
529
+ # It is not supported to create a queue without a name; some string
530
+ # (even the empty string) must be passed in the +name+ parameter.
531
+ #
532
+ # == Options
533
+ # * :passive => true | false (default false)
534
+ # If set, the server will not create the exchange if it does not
535
+ # already exist. The client can use this to check whether an exchange
536
+ # exists without modifying the server state.
537
+ #
538
+ # * :durable => true | false (default false)
539
+ # If set when creating a new queue, the queue will be marked as
540
+ # durable. Durable queues remain active when a server restarts.
541
+ # Non-durable queues (transient queues) are purged if/when a
542
+ # server restarts. Note that durable queues do not necessarily
543
+ # hold persistent messages, although it does not make sense to
544
+ # send persistent messages to a transient queue (though it is
545
+ # allowed).
546
+ #
547
+ # Again, note the durability property on a queue has no influence on
548
+ # the persistence of published messages. A durable queue containing
549
+ # transient messages will flush those messages on a restart.
550
+ #
551
+ # If the queue has already been declared, any redeclaration will
552
+ # ignore this setting. A queue may only be declared durable the
553
+ # first time when it is created.
554
+ #
555
+ # * :exclusive => true | false (default false)
556
+ # Exclusive queues may only be consumed from by the current connection.
557
+ # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
558
+ # single consumer is allowed to remove messages from this queue.
559
+ #
560
+ # The default is a shared queue. Multiple clients may consume messages
561
+ # from this queue.
562
+ #
563
+ # Attempting to redeclare an already-declared queue as :exclusive => true
564
+ # will raise MQ:Error.
565
+ #
566
+ # * :auto_delete = true | false (default false)
567
+ # If set, the queue is deleted when all consumers have finished
568
+ # using it. Last consumer can be cancelled either explicitly or because
569
+ # its channel is closed. If there was no consumer ever on the queue, it
570
+ # won't be deleted.
571
+ #
572
+ # The server waits for a short period of time before
573
+ # determining the queue is unused to give time to the client code
574
+ # to bind an exchange to it.
575
+ #
576
+ # If the queue has been previously declared, this option is ignored
577
+ # on subsequent declarations.
578
+ #
579
+ # Any remaining messages in the queue will be purged when the queue
580
+ # is deleted regardless of the message's persistence setting.
581
+ #
582
+ # * :nowait => true | false (default true)
583
+ # If set, the server will not respond to the method. The client should
584
+ # not wait for a reply method. If the server could not complete the
585
+ # method it will raise a channel or connection exception.
586
+ #
128
587
  def queue name, opts = {}
129
588
  queues[name] ||= Queue.new(self, name, opts)
130
589
  end
131
590
 
591
+ # Takes a channel, queue and optional object.
592
+ #
593
+ # The optional object may be a class name, module name or object
594
+ # instance. When given a class or module name, the object is instantiated
595
+ # during this setup. The passed queue is automatically subscribed to so
596
+ # it passes all messages (and their arguments) to the object.
597
+ #
598
+ # Marshalling and unmarshalling the objects is handled internally. This
599
+ # marshalling is subject to the same restrictions as defined in the
600
+ # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
601
+ # library. See that documentation for further reference.
602
+ #
603
+ # When the optional object is not passed, the returned rpc reference is
604
+ # used to send messages and arguments to the queue. See #method_missing
605
+ # which does all of the heavy lifting with the proxy. Some client
606
+ # elsewhere must call this method *with* the optional block so that
607
+ # there is a valid destination. Failure to do so will just enqueue
608
+ # marshalled messages that are never consumed.
609
+ #
610
+ # EM.run do
611
+ # server = MQ.rpc('hash table node', Hash)
612
+ #
613
+ # client = MQ.rpc('hash table node')
614
+ # client[:now] = Time.now
615
+ # client[:one] = 1
616
+ #
617
+ # client.values do |res|
618
+ # p 'client', :values => res
619
+ # end
620
+ #
621
+ # client.keys do |res|
622
+ # p 'client', :keys => res
623
+ # EM.stop_event_loop
624
+ # end
625
+ # end
626
+ #
132
627
  def rpc name, obj = nil
133
628
  rpcs[name] ||= RPC.new(self, name, obj)
134
629
  end
@@ -144,8 +639,8 @@ class MQ
144
639
  end
145
640
  end
146
641
 
147
- # error callback
148
-
642
+ # Define a message and callback block to be executed on all
643
+ # errors.
149
644
  def self.error msg = nil, &blk
150
645
  if blk
151
646
  @error_callback = blk
@@ -154,12 +649,16 @@ class MQ
154
649
  end
155
650
  end
156
651
 
157
- # keep track of proxy objects
158
-
652
+ # Returns a hash of all the exchange proxy objects.
653
+ #
654
+ # Not typically called by client code.
159
655
  def exchanges
160
656
  @exchanges ||= {}
161
657
  end
162
658
 
659
+ # Returns a hash of all the queue proxy objects.
660
+ #
661
+ # Not typically called by client code.
163
662
  def queues
164
663
  @queues ||= {}
165
664
  end
@@ -172,18 +671,38 @@ class MQ
172
671
  end
173
672
  end
174
673
 
674
+ # Returns a hash of all rpc proxy objects.
675
+ #
676
+ # Not typically called by client code.
175
677
  def rpcs
176
678
  @rcps ||= {}
177
679
  end
178
680
 
179
- # queue objects keyed on their consumer tags
180
-
681
+ # Queue objects keyed on their consumer tags.
682
+ #
683
+ # Not typically called by client code.
181
684
  def consumers
182
685
  @consumers ||= {}
183
686
  end
184
687
 
688
+ def reset
689
+ @deferred_status = nil
690
+ @channel = nil
691
+ initialize @connection
692
+
693
+ @consumers = {}
694
+
695
+ exs = @exchanges
696
+ @exchanges = {}
697
+ exs.each{ |_,e| e.reset } if exs
698
+
699
+ qus = @queues
700
+ @queues = {}
701
+ qus.each{ |_,q| q.reset } if qus
702
+ end
703
+
185
704
  private
186
-
705
+
187
706
  def log *args
188
707
  return unless MQ.logging
189
708
  pp args
@@ -194,21 +713,23 @@ class MQ
194
713
  alias :conn :connection
195
714
  end
196
715
 
197
- # convenience wrapper (read: HACK) for thread-local MQ object
716
+ #-- convenience wrapper (read: HACK) for thread-local MQ object
198
717
 
199
718
  class MQ
200
719
  def MQ.default
201
- # XXX clear this when connection is closed
720
+ #-- XXX clear this when connection is closed
202
721
  Thread.current[:mq] ||= MQ.new
203
722
  end
204
723
 
724
+ # Allows for calls to all MQ instance methods. This implicitly calls
725
+ # MQ.new so that a new channel is allocated for subsequent operations.
205
726
  def MQ.method_missing meth, *args, &blk
206
727
  MQ.default.__send__(meth, *args, &blk)
207
728
  end
208
729
  end
209
730
 
210
- # unique identifier
211
731
  class MQ
732
+ # unique identifier
212
733
  def MQ.id
213
734
  Thread.current[:mq_id] ||= "#{`hostname`.strip}-#{Process.pid}-#{Thread.current.object_id}"
214
735
  end