beetle 3.3.10 → 3.4.2

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.
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: