beetle 3.3.10 → 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64ab9d9173f45b670f0daa0297cf5df54335452df87813a1404dd6508115332b
4
- data.tar.gz: 640e13316b247168fa96e5b5bf138ffd0b2c430defc0778d9b87d0ca4a2a9af3
3
+ metadata.gz: '08db25367467939ff6a625b0caf2b940832b398c7988189ac2a0e91d5244a78b'
4
+ data.tar.gz: d9ffc4a722b0d86db98e81e0f9080b76e2fbd01537cbb4a6839b00665e778f19
5
5
  SHA512:
6
- metadata.gz: 3da413178c33e889be536a73ebe69b5155af9f6ba054a9349616c4d07c741b791810d6ca37e931c88c90b874f0e8034fcd797210ff8b4abda85ce96945c828de
7
- data.tar.gz: 1178a3e403b9d390a52e854f0c48f6afa3c5970eee2798d4628b4280fd02402ed3c3a913b8cb136a908f078befe775715dbee4efa36108155dfd382914f2c1b0
6
+ metadata.gz: c7f986880a871a59a74ee9e3fc63b9b384097713eedc9f106c7b799cd6b8762714d05d181665ac5aadea0675fd10f30acfbaec8fcdf921a31721c609220b95ad
7
+ data.tar.gz: 3beb91290f71cf8191f9abe0a116732e0e880a3e0e57d2c774c2c417bde4fb697b7980e2d2f6af0bdfcd601b124c7c4286a796fada014771be3bb8b049130d12
@@ -1,5 +1,24 @@
1
1
  = Release Notes
2
2
 
3
+ == Version 3.4.2
4
+ * Updated amq-protocol gem to version 2.3.2.
5
+ * Fixed a rare race condition on message handler timeouts.
6
+
7
+ == Version 3.4.1
8
+ * Updated amq-protocol gem to version 2.3.1.
9
+
10
+ == Version 3.4.0
11
+ * Require redis gem version 4.2.1. This version changes the exists to check for the
12
+ existence of multiple keys, return the number of keys in the list that exist. This
13
+ requires at least redis gem version 4.2.0, but 4.2.1 contains a bug fix for said
14
+ command.
15
+
16
+ == Version 3.3.12
17
+ * Support queue level declaration of dead letter queue message TTL.
18
+
19
+ == Version 3.3.11
20
+ * Fixed that dead lettering only works correctly with global config option.
21
+
3
22
  == Version 3.3.10
4
23
  * Support configuring RabbitMQ write timeout.
5
24
 
@@ -24,9 +24,9 @@ Gem::Specification.new do |s|
24
24
 
25
25
  s.specification_version = 3
26
26
  s.add_runtime_dependency "bunny", "~> 0.7.12"
27
- s.add_runtime_dependency "redis", ">= 2.2.2"
27
+ s.add_runtime_dependency "redis", ">= 4.2.1"
28
28
  s.add_runtime_dependency "hiredis", ">= 0.4.5"
29
- s.add_runtime_dependency "amq-protocol", "= 2.3.0"
29
+ s.add_runtime_dependency "amq-protocol", "= 2.3.2"
30
30
  s.add_runtime_dependency "amqp", "= 1.8.0"
31
31
  s.add_runtime_dependency "activesupport", ">= 2.3.4"
32
32
 
@@ -78,7 +78,8 @@ module Beetle
78
78
  end
79
79
 
80
80
  def bind_dead_letter_queue!(channel, target_queue, creation_keys = {})
81
- policy_options = @client.queues[target_queue].slice(:dead_lettering, :lazy)
81
+ policy_options = @client.queues[target_queue].slice(:dead_lettering, :lazy, :dead_lettering_msg_ttl)
82
+ policy_options[:message_ttl] = policy_options.delete(:dead_lettering_msg_ttl)
82
83
  dead_letter_queue_name = "#{target_queue}_dead_letter"
83
84
  if policy_options[:dead_lettering]
84
85
  logger.debug("Beetle: creating dead letter queue #{dead_letter_queue_name} with opts: #{creation_keys.inspect}")
@@ -88,7 +89,7 @@ module Beetle
88
89
  :queue_name => target_queue,
89
90
  :bindings => @client.bindings[target_queue],
90
91
  :dead_letter_queue_name => dead_letter_queue_name,
91
- :message_ttl => @client.config.dead_lettering_msg_ttl,
92
+ :message_ttl => policy_options[:message_ttl]
92
93
  }.merge(policy_options)
93
94
  end
94
95
 
@@ -96,7 +96,8 @@ module Beetle
96
96
  raise ConfigurationError.new("queue #{name} already configured") if queues.include?(name)
97
97
  opts = {
98
98
  :exchange => name, :key => name, :auto_delete => false, :amqp_name => name,
99
- :lazy => config.lazy_queues_enabled, :dead_lettering => config.dead_lettering_enabled
99
+ :lazy => config.lazy_queues_enabled, :dead_lettering => config.dead_lettering_enabled,
100
+ :dead_lettering_msg_ttl => config.dead_lettering_msg_ttl
100
101
  }.merge!(options.symbolize_keys)
101
102
  opts.merge! :durable => true, :passive => false, :exclusive => false
102
103
  exchange = opts.delete(:exchange).to_s
@@ -120,7 +120,7 @@ module Beetle
120
120
 
121
121
  # check whether key with given suffix exists for a given <tt>msg_id</tt>.
122
122
  def exists(msg_id, suffix)
123
- with_failover { redis.exists(key(msg_id, suffix)) }
123
+ with_failover { redis.exists?(key(msg_id, suffix)) }
124
124
  end
125
125
 
126
126
  # flush the configured redis database. useful for testing.
@@ -18,6 +18,8 @@ module Beetle
18
18
  # forcefully abort a running handler after this many seconds.
19
19
  # can be overriden when registering a handler.
20
20
  DEFAULT_HANDLER_TIMEOUT = 600.seconds
21
+ # How much extra time on top of the handler timeout we add before considering a handler timed out
22
+ TIMEOUT_GRACE_PERIOD = 10.seconds
21
23
  # how many times we should try to run a handler before giving up
22
24
  DEFAULT_HANDLER_EXECUTION_ATTEMPTS = 1
23
25
  # how many seconds we should wait before retrying handler execution
@@ -167,8 +169,8 @@ module Beetle
167
169
  end
168
170
 
169
171
  # handler timed out?
170
- def timed_out?
171
- (t = @store.get(msg_id, :timeout)) && t.to_i < now
172
+ def timed_out?(t = nil)
173
+ (t ||= @store.get(msg_id, :timeout)) && (t.to_i + TIMEOUT_GRACE_PERIOD) < now
172
174
  end
173
175
 
174
176
  # reset handler timeout in the deduplication store
@@ -187,8 +189,8 @@ module Beetle
187
189
  end
188
190
 
189
191
  # whether we should wait before running the handler
190
- def delayed?
191
- (t = @store.get(msg_id, :delay)) && t.to_i > now
192
+ def delayed?(t = nil)
193
+ (t ||= @store.get(msg_id, :delay)) && t.to_i > now
192
194
  end
193
195
 
194
196
  # store delay value in the deduplication store
@@ -207,8 +209,8 @@ module Beetle
207
209
  end
208
210
 
209
211
  # whether we have already tried running the handler as often as specified when the handler was registered
210
- def attempts_limit_reached?
211
- (limit = @store.get(msg_id, :attempts)) && limit.to_i >= attempts_limit
212
+ def attempts_limit_reached?(attempts = nil)
213
+ (attempts ||= @store.get(msg_id, :attempts)) && attempts.to_i >= attempts_limit
212
214
  end
213
215
 
214
216
  # increment number of exception occurences in the deduplication store
@@ -217,8 +219,8 @@ module Beetle
217
219
  end
218
220
 
219
221
  # whether the number of exceptions has exceeded the limit set when the handler was registered
220
- def exceptions_limit_reached?
221
- @store.get(msg_id, :exceptions).to_i > exceptions_limit
222
+ def exceptions_limit_reached?(exceptions = nil)
223
+ (exceptions ||= @store.get(msg_id, :exceptions)) && exceptions.to_i > exceptions_limit
222
224
  end
223
225
 
224
226
  def exception_accepted?
@@ -306,17 +308,17 @@ module Beetle
306
308
  if status == "completed"
307
309
  ack!
308
310
  RC::OK
309
- elsif delay && delay.to_i > now
311
+ elsif delay && delayed?(delay)
310
312
  logger.warn "Beetle: ignored delayed message (#{msg_id})!"
311
313
  RC::Delayed
312
- elsif !(timeout && timeout.to_i < now)
314
+ elsif !(timeout && timed_out?(timeout))
313
315
  RC::HandlerNotYetTimedOut
314
- elsif attempts.to_i >= attempts_limit
316
+ elsif attempts && attempts_limit_reached?(attempts)
315
317
  completed!
316
318
  ack!
317
319
  logger.warn "Beetle: reached the handler execution attempts limit: #{attempts_limit} on #{msg_id}"
318
320
  RC::AttemptsLimitReached
319
- elsif exceptions.to_i > exceptions_limit
321
+ elsif exceptions && exceptions_limit_reached?(exceptions)
320
322
  completed!
321
323
  ack!
322
324
  logger.warn "Beetle: reached the handler exceptions limit: #{exceptions_limit} on #{msg_id}"
@@ -164,7 +164,7 @@ module Beetle
164
164
  end
165
165
  http = Net::HTTP.new(uri.hostname, config.api_port)
166
166
  http.read_timeout = config.rabbitmq_api_read_timeout
167
- http.write_timeout = config.rabbitmq_api_write_timeout
167
+ http.write_timeout = config.rabbitmq_api_write_timeout if http.respond_to?(:write_timeout=)
168
168
  # don't do this in production:
169
169
  # http.set_debug_output(logger.instance_eval{ @logdev.dev })
170
170
  http.start do |instance|
@@ -180,7 +180,7 @@ module Beetle
180
180
  processor = Handler.create(handler, opts)
181
181
  result = m.process(processor)
182
182
  if result.reject?
183
- if @client.config.dead_lettering_enabled?
183
+ if @client.queues[queue_name][:dead_lettering]
184
184
  header.reject(:requeue => false)
185
185
  else
186
186
  sleep 1
@@ -1,3 +1,3 @@
1
1
  module Beetle
2
- VERSION = "3.3.10"
2
+ VERSION = "3.4.2"
3
3
  end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'time'
4
+
5
+ counts = Hash.new(0)
6
+ expiries = Hash.new{|h,k| h[k] = Hash.new(0)}
7
+ t = Time.now.to_i
8
+
9
+ File.open(ARGV[0]).each_line do |l|
10
+ parts = l.split(':')
11
+ queue = parts[1]
12
+ counts[queue] += 1
13
+ expiry = parts[4].to_i
14
+ expires_in = ((expiry - t)/(3600.0)).ceil
15
+ expiries[queue][expires_in] += 1
16
+ end
17
+
18
+ counts.to_a.sort_by{|_,v| -v}.each do |q,v|
19
+ puts "------------------------------------------------------------------"
20
+ puts "#{q}: #{v}"
21
+ puts "------------------------------------------------------------------"
22
+ expiries[q].to_a.sort_by{|k,_| -k}.each do |expiry, count|
23
+ printf "%3dh: %6d\n", expiry, count
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ queue_counts = Hash.new { |h,k| h[k] = 0 }
3
+ File.open(ARGV[0]).each_line do |l|
4
+ next if l == "lastgc" || l == "clients-last-seen"
5
+ a = l.split(':')
6
+ if a[0] == "msgid"
7
+ queue_counts[a[1]] += 1
8
+ else
9
+ queue_counts["none"] += 1
10
+ end
11
+ end
12
+ sorted_queues = queue_counts.to_a.sort_by{|a| -a[1]}
13
+ sorted_queues.each do |q,c|
14
+ puts "#{c}:#{q}"
15
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ require "redis"
3
+
4
+ redis = Redis.new(:host => "beetle-1.redis.ams2.xing.com", :port => 6379, :db => 0)
5
+
6
+ File.open(ARGV[0]).each_line.each_slice(10) do |s|
7
+ redis.pipelined do
8
+ s.each do |l|
9
+ l.chomp!
10
+ next if l =~ /^beetle:.*$/
11
+ redis.expire(l, 600)
12
+ end
13
+ end
14
+ end
@@ -79,7 +79,7 @@ module Beetle
79
79
 
80
80
  test "registering a queue should store it in the configuration with symbolized option keys and force durable=true and passive=false and set the amqp queue name" do
81
81
  @client.register_queue("some_queue", "durable" => false, "exchange" => "some_exchange")
82
- assert_equal({:durable => true, :passive => false, :lazy=>false, :dead_lettering=>false, :auto_delete => false, :exclusive => false, :amqp_name => "some_queue"}, @client.queues["some_queue"])
82
+ assert_equal({:durable => true, :passive => false, :lazy=>false, :dead_lettering=>false, :dead_lettering_msg_ttl=>1000, :auto_delete => false, :exclusive => false, :amqp_name => "some_queue"}, @client.queues["some_queue"])
83
83
  end
84
84
 
85
85
  test "registering a queue should add the queue to the list of queues of the queue's exchange" do
@@ -9,7 +9,7 @@ module Beetle
9
9
  end
10
10
 
11
11
  test "trying to delete a non existent key doesn't throw an error" do
12
- assert !@r.exists("hahahaha")
12
+ assert !@r.exists?("hahahaha")
13
13
  assert_equal 0, @r.del("hahahaha")
14
14
  end
15
15
 
@@ -75,9 +75,9 @@ module Beetle
75
75
  message.expects(:now).returns(1)
76
76
  message.set_timeout!
77
77
  assert_equal "2", @store.get(message.msg_id, :timeout)
78
- message.expects(:now).returns(2)
78
+ message.expects(:now).returns(2 + Message::TIMEOUT_GRACE_PERIOD)
79
79
  assert !message.timed_out?
80
- message.expects(:now).returns(3)
80
+ message.expects(:now).returns(3 + Message::TIMEOUT_GRACE_PERIOD)
81
81
  assert message.timed_out?
82
82
  end
83
83
 
@@ -86,9 +86,9 @@ module Beetle
86
86
  message.expects(:now).returns(0)
87
87
  message.set_timeout!
88
88
  assert_equal "#{Message::DEFAULT_HANDLER_TIMEOUT}", @store.get(message.msg_id, :timeout)
89
- message.expects(:now).returns(message.timeout)
89
+ message.expects(:now).returns(message.timeout + Message::TIMEOUT_GRACE_PERIOD)
90
90
  assert !message.timed_out?
91
- message.expects(:now).returns(Message::DEFAULT_HANDLER_TIMEOUT + 1)
91
+ message.expects(:now).returns(message.timeout + Message::TIMEOUT_GRACE_PERIOD + 1)
92
92
  assert message.timed_out?
93
93
  end
94
94
 
@@ -141,9 +141,7 @@ module Beetle
141
141
 
142
142
  message.process(@null_handler)
143
143
  keys = @store.keys(message.msg_id)
144
- keys.each do |key|
145
- assert !@store.redis.exists(key)
146
- end
144
+ assert_equal 0, @store.redis.exists(*keys)
147
145
  end
148
146
 
149
147
  test "successful processing of a non redundant message should delete all keys from the database (except the status key, which should be set to expire)" do
@@ -159,11 +157,9 @@ module Beetle
159
157
  message.process(@null_handler)
160
158
  keys = @store.keys(message.msg_id)
161
159
  status_key = keys.shift
162
- assert @store.redis.exists(status_key)
160
+ assert @store.redis.exists?(status_key)
163
161
  assert @store.redis.ttl(status_key) <= @config.redis_status_key_expiry_interval
164
- keys.each do |key|
165
- assert !@store.redis.exists(key)
166
- end
162
+ assert_equal 0, @store.redis.exists(*keys)
167
163
  end
168
164
 
169
165
  test "successful processing of a redundant message twice should delete all keys from the database" do
@@ -179,9 +175,7 @@ module Beetle
179
175
  message.process(@null_handler)
180
176
 
181
177
  keys = @store.keys(message.msg_id)
182
- keys.each do |key|
183
- assert !@store.redis.exists(key)
184
- end
178
+ assert_equal 0, @store.redis.exists(*keys)
185
179
  end
186
180
 
187
181
  test "successful processing of a redundant message twice should delete all keys from the database (except the status key, which should be set to expire)" do
@@ -199,11 +193,9 @@ module Beetle
199
193
 
200
194
  keys = @store.keys(message.msg_id)
201
195
  status_key = keys.shift
202
- assert @store.redis.exists(status_key)
196
+ assert @store.redis.exists?(status_key)
203
197
  assert @store.redis.ttl(status_key) <= @config.redis_status_key_expiry_interval
204
- keys.each do |key|
205
- assert !@store.redis.exists(key)
206
- end
198
+ assert_equal 0, @store.redis.exists(*keys)
207
199
  end
208
200
 
209
201
  test "successful processing of a redundant message once should insert all but the delay key and the exception count key into the database" do
@@ -224,13 +224,11 @@ module Beetle
224
224
  end
225
225
  end
226
226
 
227
-
228
227
  class DeadLetteringCallBackExecutionTest < Minitest::Test
229
228
  def setup
230
229
  @client = Client.new
231
- @client.config.dead_lettering_enabled = true
232
230
  @queue = "somequeue"
233
- @client.register_queue(@queue)
231
+ @client.register_queue(@queue, :dead_lettering => true)
234
232
  @sub = @client.send(:subscriber)
235
233
  mq = mock("MQ")
236
234
  mq.expects(:closing?).returns(false)
@@ -239,11 +237,7 @@ module Beetle
239
237
  @handler = Handler.create(lambda{|*args| raise @exception})
240
238
  # handler method 'processing_completed' should be called under all circumstances
241
239
  @handler.expects(:processing_completed).once
242
- @callback = @sub.send(:create_subscription_callback, "my myessage", @queue, @handler, :exceptions => 1)
243
- end
244
-
245
- def teardown
246
- @client.config.dead_lettering_enabled = false
240
+ @callback = @sub.send(:create_subscription_callback, @queue, @queue, @handler, :exceptions => 1)
247
241
  end
248
242
 
249
243
  test "should call reject on the message header when processing the handler returns true on reject? if dead lettering has been enabled" do
@@ -255,19 +249,18 @@ module Beetle
255
249
  header.expects(:reject).with(:requeue => false)
256
250
  @callback.call(header, 'foo')
257
251
  end
258
-
259
252
  end
260
253
 
261
254
  class CallBackExecutionTest < Minitest::Test
262
255
  def setup
263
- client = Client.new
256
+ @client = Client.new
264
257
  @queue = "somequeue"
265
- client.register_queue(@queue)
266
- @sub = client.send(:subscriber)
258
+ @client.register_queue(@queue)
259
+ @sub = @client.send(:subscriber)
267
260
  @exception = Exception.new "murks"
268
261
  @handler = Handler.create(lambda{|*args| raise @exception})
269
262
  @handler.instance_eval { def post_process; raise "shoot"; end }
270
- @callback = @sub.send(:create_subscription_callback, "my myessage", @queue, @handler, :exceptions => 1)
263
+ @callback = @sub.send(:create_subscription_callback, @queue, @queue, @handler, :exceptions => 1)
271
264
  end
272
265
 
273
266
  test "exceptions raised from message processing should be ignored" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beetle
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.10
4
+ version: 3.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Kaes
@@ -9,10 +9,10 @@ authors:
9
9
  - Ali Jelveh
10
10
  - Sebastian Roebke
11
11
  - Larry Baltz
12
- autorequire:
12
+ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2020-05-21 00:00:00.000000000 Z
15
+ date: 2020-12-01 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bunny
@@ -34,14 +34,14 @@ dependencies:
34
34
  requirements:
35
35
  - - ">="
36
36
  - !ruby/object:Gem::Version
37
- version: 2.2.2
37
+ version: 4.2.1
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
- version: 2.2.2
44
+ version: 4.2.1
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: hiredis
47
47
  requirement: !ruby/object:Gem::Requirement
@@ -62,14 +62,14 @@ dependencies:
62
62
  requirements:
63
63
  - - '='
64
64
  - !ruby/object:Gem::Version
65
- version: 2.3.0
65
+ version: 2.3.2
66
66
  type: :runtime
67
67
  prerelease: false
68
68
  version_requirements: !ruby/object:Gem::Requirement
69
69
  requirements:
70
70
  - - '='
71
71
  - !ruby/object:Gem::Version
72
- version: 2.3.0
72
+ version: 2.3.2
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: amqp
75
75
  requirement: !ruby/object:Gem::Requirement
@@ -337,7 +337,10 @@ files:
337
337
  - lib/beetle/redis_ext.rb
338
338
  - lib/beetle/subscriber.rb
339
339
  - lib/beetle/version.rb
340
+ - script/analyze-expiries
341
+ - script/analyze-redis-keys
340
342
  - script/console
343
+ - script/expire-keys
341
344
  - script/start_rabbit
342
345
  - test/beetle/amqp_gem_behavior_test.rb
343
346
  - test/beetle/base_test.rb
@@ -359,7 +362,7 @@ homepage: https://xing.github.com/beetle/
359
362
  licenses: []
360
363
  metadata:
361
364
  changelog_uri: https://github.com/xing/beetle/blob/master/RELEASE_NOTES.rdoc
362
- post_install_message:
365
+ post_install_message:
363
366
  rdoc_options:
364
367
  - "--charset=UTF-8"
365
368
  require_paths:
@@ -376,7 +379,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
376
379
  version: 1.3.7
377
380
  requirements: []
378
381
  rubygems_version: 3.0.8
379
- signing_key:
382
+ signing_key:
380
383
  specification_version: 3
381
384
  summary: High Availability AMQP Messaging with Redundant Queues
382
385
  test_files: