sidekiq-killswitch 1.1.0 → 1.1.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.
- 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
|