beetle 0.2.13 → 0.3.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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__))