beetle 0.2.13 → 0.3.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rcov/rcovtask'
4
+ # rake 0.9.2 hack to supress deprecation warnings caused by cucumber
5
+ include Rake::DSL if RAKEVERSION >= "0.9"
4
6
  require 'cucumber/rake/task'
5
7
 
6
8
  # 1.8/1.9 compatible way of loading lib/beetle.rb
data/beetle.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "beetle"
3
- s.version = "0.2.13"
3
+ s.version = "0.3.0.rc.1"
4
4
  s.required_rubygems_version = ">= 1.3.7"
5
5
  s.authors = ["Stefan Kaes", "Pascal Friederich", "Ali Jelveh", "Sebastian Roebke"]
6
6
  s.date = Time.now.strftime('%Y-%m-%d')
@@ -29,8 +29,7 @@ Gem::Specification.new do |s|
29
29
 
30
30
  s.specification_version = 3
31
31
  s.add_runtime_dependency("uuid4r", [">= 0.1.2"])
32
- s.add_runtime_dependency("bunny", ["= 0.6.0"])
33
- s.add_runtime_dependency("bunny-ext", [">= 0.6.5"])
32
+ s.add_runtime_dependency("bunny", ["~> 0.7.1"])
34
33
  s.add_runtime_dependency("redis", ["= 2.0.4"])
35
34
  s.add_runtime_dependency("amqp", ["~> 0.6.7"])
36
35
  s.add_runtime_dependency("activesupport", [">= 2.3.4"])
data/examples/attempts.rb CHANGED
@@ -47,7 +47,7 @@ class Handler < Beetle::Handler
47
47
  # we're stopping the event loop so this script stops after that
48
48
  def failure(result)
49
49
  super
50
- $client.stop_listening
50
+ EM.add_timer(1){$client.stop_listening}
51
51
  end
52
52
  end
53
53
 
@@ -62,5 +62,5 @@ $client.listen
62
62
 
63
63
  # error handling, if everything went right this shouldn't happen.
64
64
  if $exceptions != $max_exceptions + 1
65
- raise "something is fishy. Failed #{$exceptions} times"
65
+ raise "Something is fishy. Failed #{$exceptions} times"
66
66
  end
@@ -0,0 +1,75 @@
1
+ # attempts.rb
2
+ # this example shows you how to use the exception limiting feature of beetle
3
+ # it allows you to control the number of retries your handler will go through
4
+ # with one message before giving up on it
5
+ #
6
+ # ! check the examples/README.rdoc for information on starting your redis/rabbit !
7
+ #
8
+ # start it with ruby attempts.rb
9
+
10
+ require "rubygems"
11
+ require File.expand_path("../lib/beetle", File.dirname(__FILE__))
12
+
13
+ # set Beetle log level to info, less noisy than debug
14
+ Beetle.config.logger.level = Logger::INFO
15
+
16
+ # setup client
17
+ $client = Beetle::Client.new
18
+ $client.register_queue(:test)
19
+ $client.register_message(:test)
20
+
21
+ # purge the test queue
22
+ $client.purge(:test)
23
+
24
+ # initially, our service is online
25
+ $online = true
26
+
27
+ # declare a handler class for message processing
28
+ # in this example we've not only overwritten the process method but also the
29
+ # error and failure methods of the handler baseclass
30
+ class Handler < Beetle::Handler
31
+
32
+ # called when the handler receives the message - fail everytime
33
+ def process
34
+ puts "received message #{message.data} online=#{$online}"
35
+ unless $online
36
+ $client.pause_listening(:test)
37
+ raise "offline"
38
+ end
39
+ end
40
+
41
+ # called when handler process raised an exception
42
+ def error(exception)
43
+ puts "execution failed: #{exception}"
44
+ end
45
+
46
+ end
47
+
48
+ # publish a decent amount of messages
49
+ # 1000.times do |i|
50
+ # $client.publish(:test, i)
51
+ # end
52
+ # $client.stop_publishing
53
+
54
+ # register our handler to the message, configure it to our max_exceptions limit, we configure a delay of 0 to have it not wait before retrying
55
+ $client.register_handler(:test, Handler, :exceptions => 1, :delay => 0)
56
+
57
+ # and start our listening loop...
58
+ $client.listen do
59
+ n = 0
60
+ ptimer = EM.add_periodic_timer(0.1) do
61
+ data = (n+=1)
62
+ puts "publishing message #{data}"
63
+ $client.publish(:test, data)
64
+ end
65
+
66
+ EM.add_periodic_timer(2) do
67
+ $online = !$online
68
+ $client.resume_listening(:test) if $online
69
+ end
70
+
71
+ EM.add_timer(10) do
72
+ $client.pause_listening
73
+ EM.add_timer(1) { $client.stop_listening }
74
+ end
75
+ end
data/examples/simple.rb CHANGED
@@ -5,7 +5,7 @@
5
5
  #
6
6
  # ! check the examples/README.rdoc for information on starting your redis/rabbit !
7
7
  #
8
- # start it with ruby multiple_exchanges.rb
8
+ # start it with ruby simple.rb
9
9
 
10
10
  require "rubygems"
11
11
  require File.expand_path("../lib/beetle", File.dirname(__FILE__))
@@ -27,8 +27,8 @@ client.deduplication_store.flushdb
27
27
  # register our handler to the message, check out the message.rb for more stuff you can get from the message object
28
28
  client.register_handler(:test) {|message| puts "got message: #{message.data}"}
29
29
 
30
- # publish our message
31
- client.publish(:test, 'bam')
30
+ # publish our message (NOTE: empty message bodies don't work, most likely due to bugs in bunny/amqp)
31
+ puts client.publish(:test, 'bam')
32
32
 
33
33
  # start listening
34
34
  # this starts the event machine event loop using EM.run
@@ -0,0 +1,32 @@
1
+ # attempts.rb
2
+ # this example shows you how to use the exception limiting feature of beetle
3
+ # it allows you to control the number of retries your handler will go through
4
+ # with one message before giving up on it
5
+ #
6
+ # ! check the examples/README.rdoc for information on starting your redis/rabbit !
7
+ #
8
+ # start it with ruby attempts.rb
9
+
10
+ require "rubygems"
11
+ require File.expand_path("../lib/beetle", File.dirname(__FILE__))
12
+ require "eventmachine"
13
+
14
+ # set Beetle log level to info, less noisy than debug
15
+ Beetle.config.logger.level = Logger::INFO
16
+
17
+ # setup client
18
+ client = Beetle::Client.new
19
+ client.register_message(:test)
20
+
21
+ n = 0
22
+ EM.run do
23
+ EM.add_periodic_timer(0.1) do
24
+ data = (n+=1)
25
+ client.logger.info "publishing #{data}"
26
+ client.publish(:test, data)
27
+ end
28
+ trap("INT") do
29
+ client.stop_publishing
30
+ EM.stop_event_loop
31
+ end
32
+ end
data/lib/beetle/client.rb CHANGED
@@ -140,8 +140,7 @@ module Beetle
140
140
  # For details on handler classes see class Beetle::Handler
141
141
 
142
142
  def register_handler(queues, *args, &block)
143
- queues = Array(queues).map(&:to_s)
144
- queues.each {|q| raise UnknownQueue.new(q) unless self.queues.include?(q)}
143
+ queues = determine_queue_names(Array(queues))
145
144
  opts = args.last.is_a?(Hash) ? args.pop : {}
146
145
  handler = args.shift
147
146
  raise ArgumentError.new("too many arguments for handler registration") unless args.empty?
@@ -167,9 +166,9 @@ module Beetle
167
166
  end
168
167
 
169
168
  # publishes a message. the given options hash is merged with options given on message registration.
169
+ # WARNING: empty message bodies can lead to problems.
170
170
  def publish(message_name, data=nil, opts={})
171
- message_name = message_name.to_s
172
- raise UnknownMessage.new("unknown message #{message_name}") unless messages.include?(message_name)
171
+ message_name = validated_message_name(message_name)
173
172
  publisher.publish(message_name, data, opts)
174
173
  end
175
174
 
@@ -177,16 +176,14 @@ module Beetle
177
176
  #
178
177
  # unexpected behavior can ensue if the message gets routed to more than one recipient, so be careful.
179
178
  def rpc(message_name, data=nil, opts={})
180
- message_name = message_name.to_s
181
- raise UnknownMessage.new("unknown message #{message_name}") unless messages.include?(message_name)
179
+ message_name = validated_message_name(message_name)
182
180
  publisher.rpc(message_name, data, opts)
183
181
  end
184
182
 
185
- # purges the given queue on all configured servers
186
- def purge(queue_name)
187
- queue_name = queue_name.to_s
188
- raise UnknownQueue.new("unknown queue #{queue_name}") unless queues.include?(queue_name)
189
- publisher.purge(queue_name)
183
+ # purges the given queues on all configured servers
184
+ def purge(*queues)
185
+ queues = determine_queue_names(queues)
186
+ publisher.purge(queues)
190
187
  end
191
188
 
192
189
  # start listening to all registered queues. Calls #listen_queues internally
@@ -198,7 +195,8 @@ module Beetle
198
195
 
199
196
  # start listening to a list of queues (default to all registered queues).
200
197
  # runs the given block before entering the eventmachine loop.
201
- def listen_queues(queues=self.queues.keys, &block)
198
+ def listen_queues(*queues, &block)
199
+ queues = determine_queue_names(queues)
202
200
  subscriber.listen_queues(queues, &block)
203
201
  end
204
202
 
@@ -212,6 +210,18 @@ module Beetle
212
210
  publisher.stop
213
211
  end
214
212
 
213
+ # pause listening on a list of queues
214
+ def pause_listening(*queues)
215
+ queues = determine_queue_names(queues)
216
+ subscriber.pause_listening(queues)
217
+ end
218
+
219
+ # resume listening on a list of queues
220
+ def resume_listening(*queues)
221
+ queues = determine_queue_names(queues)
222
+ subscriber.resume_listening(queues)
223
+ end
224
+
215
225
  # traces queues without consuming them. useful for debugging message flow.
216
226
  def trace(queue_names=self.queues.keys, &block)
217
227
  queues_to_trace = self.queues.slice(*queue_names)
@@ -238,6 +248,26 @@ module Beetle
238
248
 
239
249
  private
240
250
 
251
+ def determine_queue_names(queues)
252
+ if queues.empty?
253
+ self.queues.keys
254
+ else
255
+ queues.flatten.map{|q| validated_queue_name(q)}
256
+ end
257
+ end
258
+
259
+ def validated_queue_name(queue_name)
260
+ queue_name = queue_name.to_s
261
+ raise UnknownQueue.new("unknown queue #{queue_name}") unless queues.include?(queue_name)
262
+ queue_name
263
+ end
264
+
265
+ def validated_message_name(message_name)
266
+ message_name = message_name.to_s
267
+ raise UnknownMessage.new("unknown message #{message_name}") unless messages.include?(message_name)
268
+ message_name
269
+ end
270
+
241
271
  class Configurator #:nodoc:all
242
272
  def initialize(client, options={})
243
273
  @client = client
@@ -11,13 +11,16 @@ module Beetle
11
11
  # the Message instance which caused the handler to be created
12
12
  attr_reader :message
13
13
 
14
- def self.create(block_or_handler, opts={}) #:nodoc:
15
- if block_or_handler.is_a? Handler
16
- block_or_handler
17
- elsif block_or_handler.is_a?(Class) && block_or_handler.ancestors.include?(Handler)
18
- block_or_handler.new
14
+ def self.create(handler, opts={}) #:nodoc:
15
+ if handler.is_a? Handler
16
+ # a handler instance
17
+ handler
18
+ elsif handler.is_a?(Class) && handler.ancestors.include?(Handler)
19
+ # handler class
20
+ handler.new
19
21
  else
20
- new(block_or_handler, opts)
22
+ # presumably something which responds to call
23
+ new(handler, opts)
21
24
  end
22
25
  end
23
26
 
@@ -70,6 +73,13 @@ module Beetle
70
73
  Beetle::reraise_expectation_errors!
71
74
  end
72
75
 
76
+ # should not be overriden in subclasses
77
+ def processing_completed
78
+ completed
79
+ rescue Exception
80
+ Beetle::reraise_expectation_errors!
81
+ end
82
+
73
83
  # called when handler execution raised an exception and no error callback was
74
84
  # specified when the handler instance was created
75
85
  def error(exception)
@@ -83,6 +93,12 @@ module Beetle
83
93
  logger.error "Beetle: handler has finally failed"
84
94
  end
85
95
 
96
+ # called after all normal processing has been completed. flushes the loggger, if it responds to flush.
97
+ def completed
98
+ logger.debug "Beetle: message processing completed"
99
+ logger.flush if logger.respond_to?(:flush)
100
+ end
101
+
86
102
  # returns the configured Beetle logger
87
103
  def self.logger
88
104
  Beetle.config.logger
@@ -16,7 +16,7 @@ module Beetle
16
16
  Bunny::ConnectionError, Bunny::ForcedChannelCloseError, Bunny::ForcedConnectionCloseError,
17
17
  Bunny::MessageError, Bunny::ProtocolError, Bunny::ServerDownError, Bunny::UnsubscribeError,
18
18
  Bunny::AcknowledgementError, Qrack::BufferOverflowError, Qrack::InvalidTypeError,
19
- Errno::EHOSTUNREACH, Errno::ECONNRESET
19
+ Errno::EHOSTUNREACH, Errno::ECONNRESET, Timeout::Error
20
20
  ]
21
21
  end
22
22
 
@@ -130,8 +130,12 @@ module Beetle
130
130
  [status, result]
131
131
  end
132
132
 
133
- def purge(queue_name) #:nodoc:
134
- each_server { queue(queue_name).purge rescue nil }
133
+ def purge(queue_names) #:nodoc:
134
+ each_server do
135
+ queue_names.each do |name|
136
+ queue(name).purge rescue nil
137
+ end
138
+ end
135
139
  end
136
140
 
137
141
  def stop #:nodoc:
@@ -210,7 +214,7 @@ module Beetle
210
214
  end
211
215
  end
212
216
  rescue Exception => e
213
- logger.error "Beetle: error closing down bunny #{e}"
217
+ logger.warn "Beetle: error closing down bunny #{e}"
214
218
  Beetle::reraise_expectation_errors!
215
219
  ensure
216
220
  @bunnies[@server] = nil
@@ -12,6 +12,7 @@ module Beetle
12
12
  @handlers = {}
13
13
  @amqp_connections = {}
14
14
  @mqs = {}
15
+ @subscriptions = {}
15
16
  end
16
17
 
17
18
  # the client calls this method to subscribe to a list of queues.
@@ -32,6 +33,17 @@ module Beetle
32
33
  end
33
34
  end
34
35
 
36
+ def pause_listening(queues)
37
+ each_server do
38
+ queues.each { |name| pause(name) if has_subscription?(name) }
39
+ end
40
+ end
41
+
42
+ def resume_listening(queues)
43
+ each_server do
44
+ queues.each { |name| resume(name) if has_subscription?(name) }
45
+ end
46
+ end
35
47
 
36
48
  # closes all AMQP connections and stops the eventmachine loop
37
49
  def stop! #:nodoc:
@@ -39,6 +51,7 @@ module Beetle
39
51
  EM.stop_event_loop
40
52
  else
41
53
  server, connection = @amqp_connections.shift
54
+ logger.debug "Beetle: closing connection to #{server}"
42
55
  connection.close { stop! }
43
56
  end
44
57
  end
@@ -56,10 +69,6 @@ module Beetle
56
69
  @client.bindings.slice(*queues).map{|_, opts| opts.map{|opt| opt[:exchange]}}.flatten.uniq
57
70
  end
58
71
 
59
- def exchanges_for_messages(messages)
60
- @client.messages.slice(*messages).map{|_, opts| opts[:exchange]}.uniq
61
- end
62
-
63
72
  def queues_for_exchanges(exchanges)
64
73
  @client.exchanges.slice(*exchanges).map{|_, opts| opts[:queues]}.flatten.compact.uniq
65
74
  end
@@ -91,24 +100,46 @@ module Beetle
91
100
  @mqs[server] ||= MQ.new(amqp_connection).prefetch(1)
92
101
  end
93
102
 
103
+ def subscriptions(server=@server)
104
+ @subscriptions[server] ||= {}
105
+ end
106
+
107
+ def has_subscription?(name)
108
+ subscriptions.include?(name)
109
+ end
110
+
94
111
  def subscribe(queue_name)
95
112
  error("no handler for queue #{queue_name}") unless @handlers.include?(queue_name)
96
113
  opts, handler = @handlers[queue_name]
97
114
  queue_opts = @client.queues[queue_name][:amqp_name]
98
115
  amqp_queue_name = queue_opts
99
116
  callback = create_subscription_callback(queue_name, amqp_queue_name, handler, opts)
117
+ keys = opts.slice(*SUBSCRIPTION_KEYS).merge(:key => "#", :ack => true)
100
118
  logger.debug "Beetle: subscribing to queue #{amqp_queue_name} with key # on server #{@server}"
101
119
  begin
102
- queues[queue_name].subscribe(opts.slice(*SUBSCRIPTION_KEYS).merge(:key => "#", :ack => true), &callback)
120
+ queues[queue_name].subscribe(keys, &callback)
121
+ subscriptions[queue_name] = [keys, callback]
103
122
  rescue MQ::Error
104
123
  error("Beetle: binding multiple handlers for the same queue isn't possible.")
105
124
  end
106
125
  end
107
126
 
127
+ def pause(queue_name)
128
+ return unless queues[queue_name].subscribed?
129
+ queues[queue_name].unsubscribe
130
+ end
131
+
132
+ def resume(queue_name)
133
+ return if queues[queue_name].subscribed?
134
+ keys, callback = subscriptions[queue_name]
135
+ queues[queue_name].subscribe(keys, &callback)
136
+ end
137
+
108
138
  def create_subscription_callback(queue_name, amqp_queue_name, handler, opts)
109
139
  server = @server
110
140
  lambda do |header, data|
111
141
  begin
142
+ # logger.debug "Beetle: received message"
112
143
  processor = Handler.create(handler, opts)
113
144
  message_options = opts.merge(:server => server, :store => @client.deduplication_store)
114
145
  m = Message.new(amqp_queue_name, header, data, message_options)
@@ -124,10 +155,14 @@ module Beetle
124
155
  exchange = MQ::Exchange.new(mq(server), :direct, "", :key => reply_to)
125
156
  exchange.publish(m.handler_result.to_s, :headers => {:status => status})
126
157
  end
158
+ # logger.debug "Beetle: processed message"
127
159
  rescue Exception
128
160
  Beetle::reraise_expectation_errors!
129
161
  # swallow all exceptions
130
162
  logger.error "Beetle: internal error during message processing: #{$!}: #{$!.backtrace.join("\n")}"
163
+ ensure
164
+ # processing_completed swallows all exceptions, so we don't need to protect this call
165
+ processor.processing_completed
131
166
  end
132
167
  end
133
168
  end
@@ -149,7 +184,7 @@ module Beetle
149
184
 
150
185
  def new_amqp_connection
151
186
  # FIXME: wtf, how to test that reconnection feature....
152
- con = AMQP.connect(:host => current_host, :port => current_port,
187
+ con = AMQP.connect(:host => current_host, :port => current_port, :logging => false,
153
188
  :user => Beetle.config.user, :pass => Beetle.config.password, :vhost => Beetle.config.vhost)
154
189
  con.instance_variable_set("@on_disconnect", proc{ con.__send__(:reconnect) })
155
190
  con
data/lib/beetle.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  $:.unshift(File.expand_path('..', __FILE__))
2
- require 'bunny-ext'
2
+ require 'bunny'
3
3
  require 'uuid4r'
4
4
  require 'active_support'
5
5
  require 'active_support/core_ext'
@@ -236,14 +236,14 @@ module Beetle
236
236
  test "should delegate queue purging to the publisher instance" do
237
237
  client = Client.new
238
238
  client.register_queue(:queue)
239
- client.send(:publisher).expects(:purge).with("queue").returns("ha!")
239
+ client.send(:publisher).expects(:purge).with(["queue"]).returns("ha!")
240
240
  assert_equal "ha!", client.purge("queue")
241
241
  end
242
242
 
243
243
  test "purging a queue should convert the queue name to a string" do
244
244
  client = Client.new
245
245
  client.register_queue(:queue)
246
- client.send(:publisher).expects(:purge).with("queue").returns("ha!")
246
+ client.send(:publisher).expects(:purge).with(["queue"]).returns("ha!")
247
247
  assert_equal "ha!", client.purge(:queue)
248
248
  end
249
249
 
@@ -251,6 +251,14 @@ module Beetle
251
251
  assert_raises(UnknownQueue) { Client.new.purge(:mumu) }
252
252
  end
253
253
 
254
+ test "should be possible to purge multiple queues with a single call" do
255
+ client = Client.new
256
+ client.register_queue(:queue1)
257
+ client.register_queue(:queue2)
258
+ client.send(:publisher).expects(:purge).with(["queue1","queue2"]).returns("ha!")
259
+ assert_equal "ha!", client.purge(:queue1, :queue2)
260
+ end
261
+
254
262
  test "should delegate rpc calls to the publisher instance" do
255
263
  client = Client.new
256
264
  client.register_message("deadletter")
@@ -274,7 +282,7 @@ module Beetle
274
282
  test "should delegate listening to queues to the subscriber instance" do
275
283
  client = Client.new
276
284
  client.register_queue(:test)
277
- client.send(:subscriber).expects(:listen_queues).with([:test]).yields
285
+ client.send(:subscriber).expects(:listen_queues).with(['test']).yields
278
286
  client.listen_queues([:test])
279
287
  end
280
288
 
@@ -283,12 +291,36 @@ module Beetle
283
291
  assert_raises(UnknownQueue) { Client.new.listen_queues([:foobar])}
284
292
  end
285
293
 
294
+ test "trying to pause listening on an unknown queue should raise an exception" do
295
+ client = Client.new
296
+ assert_raises(UnknownQueue) { Client.new.pause_listening(:foobar)}
297
+ end
298
+
299
+ test "trying to resume listening on an unknown queue should raise an exception" do
300
+ client = Client.new
301
+ assert_raises(UnknownQueue) { Client.new.pause_listening(:foobar)}
302
+ end
303
+
286
304
  test "should delegate stop_listening to the subscriber instance" do
287
305
  client = Client.new
288
306
  client.send(:subscriber).expects(:stop!)
289
307
  client.stop_listening
290
308
  end
291
309
 
310
+ test "should delegate pause_listening to the subscriber instance" do
311
+ client = Client.new
312
+ client.register_queue(:test)
313
+ client.send(:subscriber).expects(:pause_listening).with(%w(test))
314
+ client.pause_listening(:test)
315
+ end
316
+
317
+ test "should delegate resume_listening to the subscriber instance" do
318
+ client = Client.new
319
+ client.register_queue(:test)
320
+ client.send(:subscriber).expects(:resume_listening).with(%w(test))
321
+ client.resume_listening(:test)
322
+ end
323
+
292
324
  test "should delegate handler registration to the subscriber instance" do
293
325
  client = Client.new
294
326
  client.register_queue("huhu")
@@ -77,17 +77,18 @@ module Beetle
77
77
  assert_equal Beetle.config.logger, Handler.logger
78
78
  end
79
79
 
80
- test "default implementation of error and process and failure should not crash" do
80
+ test "default implementation of error and process and failure and completed should not crash" do
81
81
  handler = Handler.create(lambda {})
82
82
  handler.process
83
83
  handler.error('barfoo')
84
84
  handler.failure('razzmatazz')
85
+ handler.completed
85
86
  end
86
87
 
87
88
  test "should silently rescue exceptions in the process_exception call" do
88
89
  mock = mock('error handler')
89
90
  e = Exception.new
90
- mock.expects(:call).with('message', e).raises(RuntimeError)
91
+ mock.expects(:call).with('message', e).raises(Exception)
91
92
  handler = Handler.create(lambda {}, :errback => mock)
92
93
  handler.instance_variable_set(:@message, 'message')
93
94
  assert_nothing_raised {handler.process_exception(e)}
@@ -95,11 +96,17 @@ module Beetle
95
96
 
96
97
  test "should silently rescue exceptions in the process_failure call" do
97
98
  mock = mock('failure handler')
98
- mock.expects(:call).with('message', 1).raises(RuntimeError)
99
+ mock.expects(:call).with('message', 1).raises(Exception)
99
100
  handler = Handler.create(lambda {}, :failback => mock)
100
101
  handler.instance_variable_set(:@message, 'message')
101
102
  assert_nothing_raised {handler.process_failure(1)}
102
103
  end
103
104
 
105
+ test "should silently rescue exceptions in the processing_completed call" do
106
+ handler = Handler.create(lambda {})
107
+ handler.expects(:completed).raises(Exception)
108
+ assert_nothing_raised {handler.processing_completed}
109
+ end
110
+
104
111
  end
105
112
  end
@@ -274,7 +274,7 @@ module Beetle
274
274
  @pub.expects(:set_current_server).with("b").in_sequence(s)
275
275
  @pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
276
276
  queue.expects(:purge).in_sequence(s)
277
- @pub.purge("queue")
277
+ @pub.purge(["queue"])
278
278
  end
279
279
  end
280
280
 
@@ -27,7 +27,7 @@ module Beetle
27
27
  m = mock("dummy")
28
28
  expected_amqp_options = {
29
29
  :host => @sub.send(:current_host), :port => @sub.send(:current_port),
30
- :user => "guest", :pass => "guest", :vhost => "/"
30
+ :user => "guest", :pass => "guest", :vhost => "/", :logging => false
31
31
  }
32
32
  AMQP.expects(:connect).with(expected_amqp_options).returns(m)
33
33
  # TODO: smarter way to test? what triggers the amqp_connection private method call?
@@ -56,6 +56,75 @@ module Beetle
56
56
 
57
57
  end
58
58
 
59
+ class SubscriberPauseAndResumeTest < Test::Unit::TestCase
60
+ def setup
61
+ @client = Client.new
62
+ @sub = @client.send(:subscriber)
63
+ @sub.servers << "localhost:7777"
64
+ @server1, @server2 = @sub.servers
65
+ @client.register_message(:a)
66
+ @client.register_queue(:a)
67
+ @client.register_handler(%W(a)){}
68
+ end
69
+
70
+ test "should pause on all servers when a handler has been registered" do
71
+ @sub.expects(:pause).with("a").times(2)
72
+ @sub.stubs(:has_subscription?).returns(true)
73
+ @sub.pause_listening(%w(a))
74
+ end
75
+
76
+ test "should resume on all servers when a handler has been registered" do
77
+ @sub.expects(:resume).with("a").times(2)
78
+ @sub.stubs(:has_subscription?).returns(true)
79
+ @sub.resume_listening(%w(a))
80
+ end
81
+
82
+ test "should pause on no servers when no handler has been registered" do
83
+ @sub.expects(:pause).never
84
+ @sub.stubs(:has_subscription?).returns(false)
85
+ @sub.pause_listening(%w(a))
86
+ end
87
+
88
+ test "should resume on no servers when no handler has been registered" do
89
+ @sub.expects(:resume).never
90
+ @sub.stubs(:has_subscription?).returns(false)
91
+ @sub.resume_listening(%w(a))
92
+ end
93
+
94
+ test "pausing a single queue should call amqp unsubscribe" do
95
+ q = mock("queue a")
96
+ q.expects(:subscribed?).returns(true)
97
+ q.expects(:unsubscribe)
98
+ @sub.stubs(:queues).returns({"a" =>q})
99
+ @sub.__send__(:pause, "a")
100
+ end
101
+
102
+ test "pausing a single queue which is already paused should not call amqp unsubscribe" do
103
+ q = mock("queue a")
104
+ q.expects(:subscribed?).returns(false)
105
+ q.expects(:unsubscribe).never
106
+ @sub.stubs(:queues).returns({"a" =>q})
107
+ @sub.__send__(:pause, "a")
108
+ end
109
+
110
+ test "resuming a single queue should call amqp subscribe" do
111
+ q = mock("queue a")
112
+ q.expects(:subscribed?).returns(false)
113
+ q.expects(:subscribe)
114
+ @sub.stubs(:queues).returns({"a" =>q})
115
+ @sub.__send__(:resume, "a")
116
+ end
117
+
118
+ test "resuming a single queue which is already subscribed should not call amqp subscribe" do
119
+ q = mock("queue a")
120
+ q.expects(:subscribed?).returns(true)
121
+ q.expects(:subscribe).never
122
+ @sub.stubs(:queues).returns({"a" =>q})
123
+ @sub.__send__(:resume, "a")
124
+ end
125
+
126
+ end
127
+
59
128
  class AdditionalSubscriptionServersTest < Test::Unit::TestCase
60
129
  def setup
61
130
  @config = Configuration.new
@@ -160,6 +229,8 @@ module Beetle
160
229
  @sub = client.send(:subscriber)
161
230
  @exception = Exception.new "murks"
162
231
  @handler = Handler.create(lambda{|*args| raise @exception})
232
+ # handler method 'processing_completed' should be called under all circumstances
233
+ @handler.expects(:processing_completed).once
163
234
  @callback = @sub.send(:create_subscription_callback, "my myessage", @queue, @handler, :exceptions => 1)
164
235
  end
165
236
 
@@ -225,7 +296,7 @@ module Beetle
225
296
  @sub.send(:subscribe_queues, %W(a b))
226
297
  end
227
298
 
228
- test "subscribe should subscribe with a subscription callback created from the registered block" do
299
+ test "subscribe should subscribe with a subscription callback created from the registered block and remember the subscription" do
229
300
  @client.register_queue(:some_queue, :exchange => "some_exchange", :key => "some_key")
230
301
  server = @sub.server
231
302
  header = header_with_params({})
@@ -239,17 +310,21 @@ module Beetle
239
310
  end
240
311
  @sub.register_handler("some_queue", &proc)
241
312
  q = mock("QUEUE")
242
- q.expects(:subscribe).with({:ack => true, :key => "#"}).yields(header, "foo")
243
- @sub.expects(:queues).returns({"some_queue" => q})
313
+ subscription_options = {:ack => true, :key => "#"}
314
+ q.expects(:subscribe).with(subscription_options).yields(header, "foo")
315
+ @sub.expects(:queues).returns({"some_queue" => q}).twice
244
316
  @sub.send(:subscribe, "some_queue")
245
317
  assert block_called
318
+ assert @sub.__send__(:has_subscription?, "some_queue")
319
+ q.expects(:subscribe).with(subscription_options).raises(MQ::Error)
320
+ assert_raises(Error) { @sub.send(:subscribe, "some_queue") }
246
321
  end
247
322
 
248
323
  test "subscribe should fail if no handler exists for given message" do
249
324
  assert_raises(Error){ @sub.send(:subscribe, "some_queue") }
250
325
  end
251
326
 
252
- test "listeninging on queues should use eventmachine. create exchanges. bind queues. install subscribers. and yield." do
327
+ test "listening on queues should use eventmachine. create exchanges. bind queues. install subscribers. and yield." do
253
328
  @client.register_exchange(:an_exchange)
254
329
  @client.register_queue(:a_queue, :exchange => :an_exchange)
255
330
  @client.register_message(:a_message, :key => "foo", :exchange => :an_exchange)
metadata CHANGED
@@ -1,13 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beetle
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
5
- prerelease:
4
+ hash: 15424039
5
+ prerelease: 6
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 13
10
- version: 0.2.13
8
+ - 3
9
+ - 0
10
+ - rc
11
+ - 1
12
+ version: 0.3.0.rc.1
11
13
  platform: ruby
12
14
  authors:
13
15
  - Stefan Kaes
@@ -18,7 +20,7 @@ autorequire:
18
20
  bindir: bin
19
21
  cert_chain: []
20
22
 
21
- date: 2011-05-15 00:00:00 +02:00
23
+ date: 2011-08-27 00:00:00 +02:00
22
24
  default_executable: beetle
23
25
  dependencies:
24
26
  - !ruby/object:Gem::Dependency
@@ -43,36 +45,20 @@ dependencies:
43
45
  requirement: &id002 !ruby/object:Gem::Requirement
44
46
  none: false
45
47
  requirements:
46
- - - "="
48
+ - - ~>
47
49
  - !ruby/object:Gem::Version
48
- hash: 7
50
+ hash: 1
49
51
  segments:
50
52
  - 0
51
- - 6
52
- - 0
53
- version: 0.6.0
53
+ - 7
54
+ - 1
55
+ version: 0.7.1
54
56
  type: :runtime
55
57
  version_requirements: *id002
56
- - !ruby/object:Gem::Dependency
57
- name: bunny-ext
58
- prerelease: false
59
- requirement: &id003 !ruby/object:Gem::Requirement
60
- none: false
61
- requirements:
62
- - - ">="
63
- - !ruby/object:Gem::Version
64
- hash: 13
65
- segments:
66
- - 0
67
- - 6
68
- - 5
69
- version: 0.6.5
70
- type: :runtime
71
- version_requirements: *id003
72
58
  - !ruby/object:Gem::Dependency
73
59
  name: redis
74
60
  prerelease: false
75
- requirement: &id004 !ruby/object:Gem::Requirement
61
+ requirement: &id003 !ruby/object:Gem::Requirement
76
62
  none: false
77
63
  requirements:
78
64
  - - "="
@@ -84,11 +70,11 @@ dependencies:
84
70
  - 4
85
71
  version: 2.0.4
86
72
  type: :runtime
87
- version_requirements: *id004
73
+ version_requirements: *id003
88
74
  - !ruby/object:Gem::Dependency
89
75
  name: amqp
90
76
  prerelease: false
91
- requirement: &id005 !ruby/object:Gem::Requirement
77
+ requirement: &id004 !ruby/object:Gem::Requirement
92
78
  none: false
93
79
  requirements:
94
80
  - - ~>
@@ -100,11 +86,11 @@ dependencies:
100
86
  - 7
101
87
  version: 0.6.7
102
88
  type: :runtime
103
- version_requirements: *id005
89
+ version_requirements: *id004
104
90
  - !ruby/object:Gem::Dependency
105
91
  name: activesupport
106
92
  prerelease: false
107
- requirement: &id006 !ruby/object:Gem::Requirement
93
+ requirement: &id005 !ruby/object:Gem::Requirement
108
94
  none: false
109
95
  requirements:
110
96
  - - ">="
@@ -116,11 +102,11 @@ dependencies:
116
102
  - 4
117
103
  version: 2.3.4
118
104
  type: :runtime
119
- version_requirements: *id006
105
+ version_requirements: *id005
120
106
  - !ruby/object:Gem::Dependency
121
107
  name: daemons
122
108
  prerelease: false
123
- requirement: &id007 !ruby/object:Gem::Requirement
109
+ requirement: &id006 !ruby/object:Gem::Requirement
124
110
  none: false
125
111
  requirements:
126
112
  - - ">="
@@ -132,11 +118,11 @@ dependencies:
132
118
  - 10
133
119
  version: 1.0.10
134
120
  type: :runtime
135
- version_requirements: *id007
121
+ version_requirements: *id006
136
122
  - !ruby/object:Gem::Dependency
137
123
  name: rake
138
124
  prerelease: false
139
- requirement: &id008 !ruby/object:Gem::Requirement
125
+ requirement: &id007 !ruby/object:Gem::Requirement
140
126
  none: false
141
127
  requirements:
142
128
  - - ">="
@@ -148,11 +134,11 @@ dependencies:
148
134
  - 7
149
135
  version: 0.8.7
150
136
  type: :development
151
- version_requirements: *id008
137
+ version_requirements: *id007
152
138
  - !ruby/object:Gem::Dependency
153
139
  name: mocha
154
140
  prerelease: false
155
- requirement: &id009 !ruby/object:Gem::Requirement
141
+ requirement: &id008 !ruby/object:Gem::Requirement
156
142
  none: false
157
143
  requirements:
158
144
  - - ">="
@@ -162,11 +148,11 @@ dependencies:
162
148
  - 0
163
149
  version: "0"
164
150
  type: :development
165
- version_requirements: *id009
151
+ version_requirements: *id008
166
152
  - !ruby/object:Gem::Dependency
167
153
  name: rcov
168
154
  prerelease: false
169
- requirement: &id010 !ruby/object:Gem::Requirement
155
+ requirement: &id009 !ruby/object:Gem::Requirement
170
156
  none: false
171
157
  requirements:
172
158
  - - ">="
@@ -176,11 +162,11 @@ dependencies:
176
162
  - 0
177
163
  version: "0"
178
164
  type: :development
179
- version_requirements: *id010
165
+ version_requirements: *id009
180
166
  - !ruby/object:Gem::Dependency
181
167
  name: redgreen
182
168
  prerelease: false
183
- requirement: &id011 !ruby/object:Gem::Requirement
169
+ requirement: &id010 !ruby/object:Gem::Requirement
184
170
  none: false
185
171
  requirements:
186
172
  - - ">="
@@ -190,11 +176,11 @@ dependencies:
190
176
  - 0
191
177
  version: "0"
192
178
  type: :development
193
- version_requirements: *id011
179
+ version_requirements: *id010
194
180
  - !ruby/object:Gem::Dependency
195
181
  name: wirble
196
182
  prerelease: false
197
- requirement: &id012 !ruby/object:Gem::Requirement
183
+ requirement: &id011 !ruby/object:Gem::Requirement
198
184
  none: false
199
185
  requirements:
200
186
  - - ">="
@@ -204,11 +190,11 @@ dependencies:
204
190
  - 0
205
191
  version: "0"
206
192
  type: :development
207
- version_requirements: *id012
193
+ version_requirements: *id011
208
194
  - !ruby/object:Gem::Dependency
209
195
  name: cucumber
210
196
  prerelease: false
211
- requirement: &id013 !ruby/object:Gem::Requirement
197
+ requirement: &id012 !ruby/object:Gem::Requirement
212
198
  none: false
213
199
  requirements:
214
200
  - - ">="
@@ -220,11 +206,11 @@ dependencies:
220
206
  - 2
221
207
  version: 0.7.2
222
208
  type: :development
223
- version_requirements: *id013
209
+ version_requirements: *id012
224
210
  - !ruby/object:Gem::Dependency
225
211
  name: daemon_controller
226
212
  prerelease: false
227
- requirement: &id014 !ruby/object:Gem::Requirement
213
+ requirement: &id013 !ruby/object:Gem::Requirement
228
214
  none: false
229
215
  requirements:
230
216
  - - ">="
@@ -234,7 +220,7 @@ dependencies:
234
220
  - 0
235
221
  version: "0"
236
222
  type: :development
237
- version_requirements: *id014
223
+ version_requirements: *id013
238
224
  description: A highly available, reliable messaging infrastructure
239
225
  email: developers@xing.com
240
226
  executables:
@@ -254,9 +240,11 @@ files:
254
240
  - examples/handling_exceptions.rb
255
241
  - examples/multiple_exchanges.rb
256
242
  - examples/multiple_queues.rb
243
+ - examples/pause_and_resume.rb
257
244
  - examples/redundant.rb
258
245
  - examples/rpc.rb
259
246
  - examples/simple.rb
247
+ - examples/test_publisher.rb
260
248
  - lib/beetle/base.rb
261
249
  - lib/beetle/client.rb
262
250
  - lib/beetle/commands/configuration_client.rb
@@ -287,7 +275,6 @@ files:
287
275
  - features/support/test_daemons/redis_configuration_client.rb
288
276
  - features/support/test_daemons/redis_configuration_server.rb
289
277
  - script/console
290
- - script/console~
291
278
  - script/start_rabbit
292
279
  - beetle.gemspec
293
280
  - Rakefile
data/script/console~ DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env ruby
2
- $:.unshift(File.expand_path("../lib",__FILE__))