sidekiq-killswitch 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/Gemfile +2 -0
- data/lib/sidekiq/killswitch/config.rb +25 -23
- data/lib/sidekiq/killswitch/middleware/client.rb +1 -1
- data/lib/sidekiq/killswitch/middleware/server.rb +4 -2
- data/lib/sidekiq/killswitch/version.rb +1 -1
- data/lib/sidekiq/killswitch.rb +67 -59
- data/spec/killswitch_spec.rb +2 -2
- data/spec/spec_helper.rb +2 -0
- data/spec/web_spec.rb +6 -1
- metadata +3 -6
- data/lib/sidekiq/extensions/dead_set.rb +0 -23
- data/spec/dead_set_spec.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2c1e5555f67df5c51420ca3946edcf8786c622f7ee6d7c2ef238a3c2e42f639
|
4
|
+
data.tar.gz: 5607f5785571de88fae01a9abca4637bbeb35a2418db9cda87a29f0c6f5a1bcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21e69f7d202a0c5f186d4f317bcb05ec8031522bd24c05bc7720be745e12f11280a5fd1f0902cf431dce750a63c9167409b36c5d4e92b9957fe916031f3a9699
|
7
|
+
data.tar.gz: 4399541da941edfef9a83eb555e80c1c5e98ffcb76160eaf487a06bc5415af4bcb4b5c4d0dcd299ad68d292509538f2b387b80a230fac56389b0431f0e3f1b31
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
### 1.1.1 / 2024-08-27
|
2
|
+
* Support for `redis-client` client (used by Sidekiq 7)
|
3
|
+
* Remove `Sidekiq::DeadSet#kill` extension.
|
4
|
+
* `module Sidekiq::Killswitch; end` replaced with `module Sidekiq; module Killswitch; ...`.
|
5
|
+
* We're now using `warn` instead of `info` to log worker blackholed/dead_queued messaged.
|
6
|
+
|
1
7
|
### 1.1.0 / 2024-07-01
|
2
8
|
* Add in warning banner to Sidekiq UI so users are linked to docs and informed of the difference of sending jobs to blackhole vs. dead queue
|
3
9
|
|
data/Gemfile
CHANGED
@@ -1,36 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Sidekiq
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Sidekiq
|
4
|
+
module Killswitch
|
5
|
+
class Config
|
6
|
+
attr_accessor :web_ui_worker_validator
|
7
|
+
attr_writer :logger
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def initialize
|
10
|
+
self.web_ui_worker_validator = ->(worker_name) { !worker_name.nil? && worker_name != '' }
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def logger
|
14
|
+
@logger ||= Sidekiq.logger
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def validate_worker_class_in_web
|
18
|
+
self.web_ui_worker_validator = proc do |worker_name|
|
19
|
+
begin
|
20
|
+
constantize(worker_name).include?(Sidekiq::Worker)
|
21
|
+
rescue NameError
|
22
|
+
false
|
23
|
+
end
|
22
24
|
end
|
23
25
|
end
|
24
|
-
end
|
25
26
|
|
26
|
-
|
27
|
+
private
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
def constantize(str)
|
30
|
+
names = str.split('::')
|
31
|
+
names.shift if names.empty? || names.first.empty?
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
names.inject(Object) do |constant, name|
|
34
|
+
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
35
|
+
end
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
@@ -8,7 +8,7 @@ module Sidekiq
|
|
8
8
|
worker_name = Sidekiq::Killswitch.class_to_str(worker_class)
|
9
9
|
|
10
10
|
if Killswitch.blackhole_worker?(worker_name)
|
11
|
-
Killswitch.logger.
|
11
|
+
Killswitch.logger.warn "#{worker_name} is currently disabled. Job #{job} was not executed."
|
12
12
|
false
|
13
13
|
else
|
14
14
|
yield
|
@@ -4,14 +4,16 @@ module Sidekiq
|
|
4
4
|
module Killswitch
|
5
5
|
module Middleware
|
6
6
|
class Server
|
7
|
+
include Sidekiq::ServerMiddleware
|
8
|
+
|
7
9
|
def call(worker, job, _queue)
|
8
10
|
serialized_job = Sidekiq.dump_json(job)
|
9
11
|
|
10
12
|
if Killswitch.dead_queue_worker?(worker.class)
|
11
13
|
DeadSet.new.kill(serialized_job)
|
12
|
-
Killswitch.logger.
|
14
|
+
Killswitch.logger.warn "#{worker.class.name} marked as dead queue worker. Job #{serialized_job} was killed."
|
13
15
|
elsif Killswitch.blackhole_worker?(worker.class)
|
14
|
-
Killswitch.logger.
|
16
|
+
Killswitch.logger.warn "#{worker.class.name} is currently disabled. Job #{serialized_job} was not executed."
|
15
17
|
else
|
16
18
|
yield
|
17
19
|
end
|
data/lib/sidekiq/killswitch.rb
CHANGED
@@ -1,92 +1,100 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'sidekiq/extensions/dead_set'
|
3
2
|
require 'sidekiq/killswitch/config'
|
4
3
|
require 'sidekiq/killswitch/middleware/server'
|
5
4
|
require 'sidekiq/killswitch/middleware/client'
|
6
5
|
|
7
|
-
module Sidekiq
|
8
|
-
|
9
|
-
|
6
|
+
module Sidekiq
|
7
|
+
module Killswitch
|
8
|
+
BLACKHOLE_WORKERS_KEY_NAME = 'sidekiq.disabled-workers'
|
9
|
+
DEAD_QUEUE_WORKERS_KEY_NAME = 'sidekiq.dead-queue-workers'
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
class << self
|
12
|
+
def config
|
13
|
+
@config ||= Config.new
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def configure(&block)
|
17
|
+
yield config
|
18
|
+
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
def logger
|
21
|
+
config.logger
|
22
|
+
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def redis_pool(&block)
|
25
|
+
Sidekiq.redis(&block)
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
def blackhole_add_worker(worker_name)
|
29
|
+
worker_name = class_to_str(worker_name)
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
redis_pool do |redis|
|
32
|
+
redis.hset(BLACKHOLE_WORKERS_KEY_NAME, worker_name, Time.now.to_s)
|
33
|
+
end
|
34
|
+
logger.warn "#{worker_name} added to blackhole workers"
|
33
35
|
end
|
34
|
-
logger.warn "#{worker_name} added to blackhole workers"
|
35
|
-
end
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
def blackhole_remove_worker(worker_name)
|
38
|
+
worker_name = class_to_str(worker_name)
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
redis_pool do |redis|
|
41
|
+
redis.hdel(BLACKHOLE_WORKERS_KEY_NAME, worker_name)
|
42
|
+
end
|
43
|
+
logger.warn "#{worker_name} removed from blackhole workers"
|
42
44
|
end
|
43
|
-
logger.warn "#{worker_name} removed from blackhole workers"
|
44
|
-
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
def blackhole_worker?(worker_name)
|
47
|
+
redis_pool do |redis|
|
48
|
+
is_true_compat(redis.hexists(BLACKHOLE_WORKERS_KEY_NAME, class_to_str(worker_name)))
|
49
|
+
end
|
49
50
|
end
|
50
|
-
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
def blackhole_workers
|
53
|
+
redis_pool do |redis|
|
54
|
+
redis.hgetall(BLACKHOLE_WORKERS_KEY_NAME)
|
55
|
+
end
|
55
56
|
end
|
56
|
-
end
|
57
57
|
|
58
|
-
|
59
|
-
|
58
|
+
def dead_queue_add_worker(worker_name)
|
59
|
+
worker_name = class_to_str(worker_name)
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
redis_pool do |redis|
|
62
|
+
redis.hset(DEAD_QUEUE_WORKERS_KEY_NAME, worker_name, Time.now.to_s)
|
63
|
+
end
|
64
|
+
logger.warn "#{worker_name} added to dead queue workers"
|
63
65
|
end
|
64
|
-
logger.warn "#{worker_name} added to dead queue workers"
|
65
|
-
end
|
66
66
|
|
67
|
-
|
68
|
-
|
67
|
+
def dead_queue_remove_worker(worker_name)
|
68
|
+
worker_name = class_to_str(worker_name)
|
69
69
|
|
70
|
-
|
71
|
-
|
70
|
+
redis_pool do |redis|
|
71
|
+
redis.hdel(DEAD_QUEUE_WORKERS_KEY_NAME, worker_name)
|
72
|
+
end
|
73
|
+
logger.warn "#{worker_name} removed from dead queue workers"
|
72
74
|
end
|
73
|
-
logger.warn "#{worker_name} removed from dead queue workers"
|
74
|
-
end
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
def dead_queue_worker?(worker_name)
|
77
|
+
redis_pool do |redis|
|
78
|
+
is_true_compat(redis.hexists(DEAD_QUEUE_WORKERS_KEY_NAME, class_to_str(worker_name)))
|
79
|
+
end
|
79
80
|
end
|
80
|
-
end
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
def dead_queue_workers
|
83
|
+
redis_pool do |redis|
|
84
|
+
redis.hgetall(DEAD_QUEUE_WORKERS_KEY_NAME)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def class_to_str(class_or_string)
|
89
|
+
class_or_string.is_a?(String) ? class_or_string : class_or_string.name
|
85
90
|
end
|
86
|
-
end
|
87
91
|
|
88
|
-
|
89
|
-
|
92
|
+
private
|
93
|
+
|
94
|
+
def is_true_compat(redis_result_value)
|
95
|
+
# Compatibility method for `redis` and `redis-client`.
|
96
|
+
redis_result_value == 1 || redis_result_value == true
|
97
|
+
end
|
90
98
|
end
|
91
99
|
end
|
92
100
|
end
|
data/spec/killswitch_spec.rb
CHANGED
@@ -65,7 +65,7 @@ RSpec.describe Sidekiq::Killswitch do
|
|
65
65
|
Sidekiq::Killswitch.blackhole_remove_worker(worker_name)
|
66
66
|
|
67
67
|
Sidekiq::Killswitch.redis_pool do |redis|
|
68
|
-
expect(redis.hexists(Sidekiq::Killswitch::BLACKHOLE_WORKERS_KEY_NAME, worker_name)).to
|
68
|
+
expect(redis.hexists(Sidekiq::Killswitch::BLACKHOLE_WORKERS_KEY_NAME, worker_name)).to eq(0)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -113,7 +113,7 @@ RSpec.describe Sidekiq::Killswitch do
|
|
113
113
|
Sidekiq::Killswitch.dead_queue_remove_worker(worker_name)
|
114
114
|
|
115
115
|
Sidekiq::Killswitch.redis_pool do |redis|
|
116
|
-
expect(redis.hexists(Sidekiq::Killswitch::DEAD_QUEUE_WORKERS_KEY_NAME, worker_name)).to
|
116
|
+
expect(redis.hexists(Sidekiq::Killswitch::DEAD_QUEUE_WORKERS_KEY_NAME, worker_name)).to eq(0)
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'rspec'
|
3
3
|
require 'logger'
|
4
|
+
require 'rack/test'
|
4
5
|
require 'sidekiq/testing'
|
5
6
|
require 'sidekiq/killswitch'
|
6
7
|
|
7
8
|
ENV['RACK_ENV'] = 'test' # Disable CSRF protection for Sidekiq Web app
|
9
|
+
$TESTING = true
|
8
10
|
|
9
11
|
Sidekiq::Killswitch.configure do |config|
|
10
12
|
config.logger = Logger.new('/dev/null')
|
data/spec/web_spec.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'spec_helper'
|
3
|
-
require 'rack/test'
|
4
3
|
require 'rspec-html-matchers'
|
5
4
|
require 'sidekiq/killswitch/web'
|
6
5
|
|
@@ -10,6 +9,12 @@ RSpec.describe Sidekiq::Killswitch::Web do
|
|
10
9
|
|
11
10
|
let(:app) { Sidekiq::Web }
|
12
11
|
|
12
|
+
before do
|
13
|
+
# Hash is not a real session, but it seems to provide a session-compatible interface,
|
14
|
+
# and we're only using #[] and #delete methods.
|
15
|
+
env "rack.session", {}
|
16
|
+
end
|
17
|
+
|
13
18
|
def expect_redirect_to_root_page(response)
|
14
19
|
expect(response.status).to be(302)
|
15
20
|
expect(response.headers['Location']).to eq("http://#{rack_mock_session.default_host}/kill-switches")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-killswitch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuriy Naidyon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|
@@ -96,7 +96,6 @@ files:
|
|
96
96
|
- Rakefile
|
97
97
|
- examples/killswitch-web-ui.png
|
98
98
|
- examples/square-logo.png
|
99
|
-
- lib/sidekiq/extensions/dead_set.rb
|
100
99
|
- lib/sidekiq/killswitch.rb
|
101
100
|
- lib/sidekiq/killswitch/config.rb
|
102
101
|
- lib/sidekiq/killswitch/middleware/client.rb
|
@@ -106,7 +105,6 @@ files:
|
|
106
105
|
- lib/sidekiq/killswitch/web.rb
|
107
106
|
- sidekiq-killswitch.gemspec
|
108
107
|
- spec/config_spec.rb
|
109
|
-
- spec/dead_set_spec.rb
|
110
108
|
- spec/killswitch_spec.rb
|
111
109
|
- spec/middleware/client_spec.rb
|
112
110
|
- spec/middleware/server_spec.rb
|
@@ -134,13 +132,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
132
|
- !ruby/object:Gem::Version
|
135
133
|
version: '0'
|
136
134
|
requirements: []
|
137
|
-
rubygems_version: 3.
|
135
|
+
rubygems_version: 3.3.26
|
138
136
|
signing_key:
|
139
137
|
specification_version: 4
|
140
138
|
summary: Cross-host Sidekiq worker killswitches
|
141
139
|
test_files:
|
142
140
|
- spec/config_spec.rb
|
143
|
-
- spec/dead_set_spec.rb
|
144
141
|
- spec/killswitch_spec.rb
|
145
142
|
- spec/middleware/client_spec.rb
|
146
143
|
- spec/middleware/server_spec.rb
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'sidekiq/api'
|
3
|
-
|
4
|
-
# this is a monkey-patch!
|
5
|
-
# TODO: delete this patch after we explicitly depend on Sidekiq version that
|
6
|
-
# includes this PR: https://github.com/mperham/sidekiq/pull/3573
|
7
|
-
|
8
|
-
module Sidekiq
|
9
|
-
module DeadSetKill
|
10
|
-
def kill(message)
|
11
|
-
now = Time.now.to_f
|
12
|
-
Sidekiq.redis do |conn|
|
13
|
-
conn.multi do
|
14
|
-
conn.zadd(name, now.to_f.to_s, message)
|
15
|
-
conn.zremrangebyscore(name, '-inf', now - self.class.timeout)
|
16
|
-
conn.zremrangebyrank(name, 0, - self.class.max_jobs)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
Sidekiq::DeadSet.include(Sidekiq::DeadSetKill) unless Sidekiq::DeadSet.method_defined?(:kill)
|
data/spec/dead_set_spec.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'spec_helper'
|
3
|
-
|
4
|
-
RSpec.describe Sidekiq::DeadSet do
|
5
|
-
let(:dead_set) { Sidekiq::DeadSet.new }
|
6
|
-
|
7
|
-
describe '#kill' do
|
8
|
-
it 'should put passed serialized job to the "dead" sorted set' do
|
9
|
-
serialized_job = Sidekiq.dump_json(jid: '123123', class: 'SomeWorker', args: [])
|
10
|
-
dead_set.kill(serialized_job)
|
11
|
-
|
12
|
-
expect(dead_set.find_job('123123').value).to eq(serialized_job)
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'should remove dead jobs older than Sidekiq::DeadSet.timeout' do
|
16
|
-
allow(Sidekiq::DeadSet).to receive(:timeout).and_return(10)
|
17
|
-
time_now = Time.now
|
18
|
-
|
19
|
-
stub_time_now(time_now - 11)
|
20
|
-
dead_set.kill(Sidekiq.dump_json({jid: '000103', class: 'MyWorker3', args: []})) # the oldest
|
21
|
-
|
22
|
-
stub_time_now(time_now - 9)
|
23
|
-
dead_set.kill(Sidekiq.dump_json({jid: '000102', class: 'MyWorker2', args: []}))
|
24
|
-
|
25
|
-
stub_time_now(time_now)
|
26
|
-
dead_set.kill(Sidekiq.dump_json({jid: '000101', class: 'MyWorker1', args: []}))
|
27
|
-
|
28
|
-
stub_time_now(time_now)
|
29
|
-
|
30
|
-
expect(dead_set.find_job('000103')).to be_falsey
|
31
|
-
expect(dead_set.find_job('000102')).to be_truthy
|
32
|
-
expect(dead_set.find_job('000101')).to be_truthy
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'should remove all but last Sidekiq::DeadSet.max_jobs-1 jobs' do
|
36
|
-
allow(Sidekiq::DeadSet).to receive(:max_jobs).and_return(3)
|
37
|
-
|
38
|
-
dead_set.kill(Sidekiq.dump_json({jid: '000101', class: 'MyWorker1', args: []}))
|
39
|
-
dead_set.kill(Sidekiq.dump_json({jid: '000102', class: 'MyWorker2', args: []}))
|
40
|
-
dead_set.kill(Sidekiq.dump_json({jid: '000103', class: 'MyWorker3', args: []}))
|
41
|
-
|
42
|
-
expect(dead_set.find_job('000101')).to be_falsey
|
43
|
-
expect(dead_set.find_job('000102')).to be_truthy
|
44
|
-
expect(dead_set.find_job('000103')).to be_truthy
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|