sidekiq-mem-warden 0.1.2 → 0.1.3
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/CHANGELOG.md +4 -1
- data/Gemfile.lock +16 -8
- data/README.md +15 -9
- data/lib/sidekiq/mem/warden/version.rb +2 -2
- data/lib/sidekiq/mem/warden.rb +92 -104
- data/sidekiq-mem-warden.gemspec +2 -1
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1a5f747821df9e7e8d6f2b15a195b11ef025b2f37210fb1c4a7e43ad88757e9
|
|
4
|
+
data.tar.gz: 42fb14442d1d7b9f9c74950f539c8e3ca2dc844657c2246a9940cb2e18498d6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7fbf5b2e812b1be8a361b74d6417111a3cf05dc60cf549ee1d73c7d67a7c8f535f689804f93204fa05d57d6af4df5c697f891ec782a2d542df648f3677b79f3b
|
|
7
|
+
data.tar.gz: 2b861f4c40a464a6c81a473f02762143d40538117dcc98b56ec31b835b17b715a9df9b0d33305cd4bd40e2d4d9b63ec0ef23eb393e07267a82956cad3dbebff2
|
data/CHANGELOG.md
CHANGED
|
@@ -8,4 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## 0.1.2
|
|
10
10
|
- Default memory limit set to 1 GB (1024 MB).
|
|
11
|
-
- Default
|
|
11
|
+
- Default grace time set to 5 minutes (300 seconds).
|
|
12
|
+
|
|
13
|
+
## 0.1.3
|
|
14
|
+
- Replaced the internal watchdog with Sidekiq server middleware based on sidekiq-worker-killer.
|
data/Gemfile.lock
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
sidekiq-mem-warden (0.1.
|
|
5
|
-
|
|
4
|
+
sidekiq-mem-warden (0.1.3)
|
|
5
|
+
get_process_mem (>= 0.2.0)
|
|
6
|
+
sidekiq (>= 7.0)
|
|
6
7
|
|
|
7
8
|
GEM
|
|
8
9
|
remote: https://rubygems.org/
|
|
9
10
|
specs:
|
|
11
|
+
bigdecimal (3.3.1)
|
|
12
|
+
concurrent-ruby (1.3.6)
|
|
10
13
|
connection_pool (2.5.3)
|
|
11
14
|
diff-lcs (1.6.2)
|
|
15
|
+
ffi (1.17.3-arm64-darwin)
|
|
16
|
+
get_process_mem (1.0.0)
|
|
17
|
+
bigdecimal (>= 2.0)
|
|
18
|
+
ffi (~> 1.0)
|
|
12
19
|
rack (2.2.17)
|
|
13
20
|
rake (13.3.0)
|
|
14
|
-
redis (
|
|
21
|
+
redis-client (0.26.2)
|
|
22
|
+
connection_pool
|
|
15
23
|
rspec (3.13.1)
|
|
16
24
|
rspec-core (~> 3.13.0)
|
|
17
25
|
rspec-expectations (~> 3.13.0)
|
|
@@ -25,14 +33,14 @@ GEM
|
|
|
25
33
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
26
34
|
rspec-support (~> 3.13.0)
|
|
27
35
|
rspec-support (3.13.6)
|
|
28
|
-
sidekiq (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
sidekiq (7.1.6)
|
|
37
|
+
concurrent-ruby (< 2)
|
|
38
|
+
connection_pool (>= 2.3.0)
|
|
39
|
+
rack (>= 2.2.4)
|
|
40
|
+
redis-client (>= 0.14.0)
|
|
32
41
|
|
|
33
42
|
PLATFORMS
|
|
34
43
|
arm64-darwin-24
|
|
35
|
-
ruby
|
|
36
44
|
|
|
37
45
|
DEPENDENCIES
|
|
38
46
|
bundler (>= 1.17)
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sidekiq::Mem::Warden
|
|
2
2
|
|
|
3
|
-
Sidekiq::Mem::Warden is a tiny
|
|
3
|
+
Sidekiq::Mem::Warden is a tiny Sidekiq server middleware that watches RSS and shuts down bloated processes after a grace period, allowing your process manager to restart them.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -26,21 +26,23 @@ Configure it in your Sidekiq server process:
|
|
|
26
26
|
require "sidekiq/mem/warden"
|
|
27
27
|
|
|
28
28
|
Sidekiq.configure_server do |config|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
config.server_middleware do |chain|
|
|
30
|
+
chain.add Sidekiq::Mem::Warden,
|
|
31
|
+
max_rss: 1024,
|
|
32
|
+
grace_time: 300,
|
|
33
|
+
shutdown_wait: 30,
|
|
34
|
+
kill_signal: "SIGKILL",
|
|
35
|
+
gc: true,
|
|
36
|
+
skip_shutdown_if: ->(worker, job, queue) { false },
|
|
37
|
+
on_shutdown: ->(worker, job, queue) { nil }
|
|
34
38
|
end
|
|
35
|
-
|
|
36
|
-
Sidekiq::Mem::Warden.install!(config)
|
|
37
39
|
end
|
|
38
40
|
```
|
|
39
41
|
|
|
40
42
|
Operational notes:
|
|
41
43
|
|
|
42
44
|
- The warden runs inside each Sidekiq process.
|
|
43
|
-
- When
|
|
45
|
+
- When RSS exceeds `max_rss`, it quiets the process, waits for jobs to finish up to `grace_time`, then stops and sends `kill_signal`.
|
|
44
46
|
- Ensure your supervisor (systemd, Kubernetes, etc.) is set to restart the process.
|
|
45
47
|
|
|
46
48
|
## Development
|
|
@@ -53,6 +55,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
53
55
|
|
|
54
56
|
Bug reports and pull requests are welcome on GitHub at https://github.com/tedaford/sidekiq-mem-warden.
|
|
55
57
|
|
|
58
|
+
## Credits
|
|
59
|
+
|
|
60
|
+
This gem is heavily based on the archived MIT-licensed `sidekiq-worker-killer` project.
|
|
61
|
+
|
|
56
62
|
## License
|
|
57
63
|
|
|
58
64
|
The gem is available as open source under the terms of the GNU General Public License v2.0.
|
data/lib/sidekiq/mem/warden.rb
CHANGED
|
@@ -1,131 +1,119 @@
|
|
|
1
|
+
require "get_process_mem"
|
|
1
2
|
require "sidekiq"
|
|
3
|
+
require "sidekiq/api"
|
|
4
|
+
begin
|
|
5
|
+
require "sidekiq/middleware/modules"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
end
|
|
2
8
|
require "sidekiq/mem/warden/version"
|
|
3
9
|
|
|
4
10
|
module Sidekiq
|
|
5
11
|
module Mem
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
class Warden
|
|
13
|
+
include Sidekiq::ServerMiddleware if defined?(Sidekiq::ServerMiddleware)
|
|
14
|
+
|
|
15
|
+
MUTEX = Mutex.new
|
|
16
|
+
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
@max_rss = options.fetch(:max_rss, 1024)
|
|
19
|
+
@grace_time = options.fetch(:grace_time, 300)
|
|
20
|
+
@shutdown_wait = options.fetch(:shutdown_wait, 30)
|
|
21
|
+
@kill_signal = options.fetch(:kill_signal, "SIGKILL")
|
|
22
|
+
@gc = options.fetch(:gc, true)
|
|
23
|
+
@skip_shutdown = options.fetch(:skip_shutdown_if, proc { false })
|
|
24
|
+
@on_shutdown = options.fetch(:on_shutdown, nil)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(worker, job, queue)
|
|
28
|
+
yield
|
|
29
|
+
|
|
30
|
+
return unless @max_rss.to_i > 0
|
|
31
|
+
return unless current_rss > @max_rss
|
|
32
|
+
|
|
33
|
+
GC.start(full_mark: true, immediate_sweep: true) if @gc
|
|
34
|
+
return unless current_rss > @max_rss
|
|
35
|
+
|
|
36
|
+
if skip_shutdown?(worker, job, queue)
|
|
37
|
+
warn "current RSS #{current_rss} exceeds maximum RSS #{@max_rss}, " \
|
|
38
|
+
"however shutdown will be ignored"
|
|
39
|
+
return
|
|
18
40
|
end
|
|
41
|
+
|
|
42
|
+
warn "current RSS #{current_rss} of #{identity} exceeds maximum RSS #{@max_rss}"
|
|
43
|
+
run_shutdown_hook(worker, job, queue)
|
|
44
|
+
request_shutdown
|
|
19
45
|
end
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def run_shutdown_hook(worker, job, queue)
|
|
50
|
+
@on_shutdown.respond_to?(:call) && @on_shutdown.call(worker, job, queue)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def skip_shutdown?(worker, job, queue)
|
|
54
|
+
@skip_shutdown.respond_to?(:call) && @skip_shutdown.call(worker, job, queue)
|
|
23
55
|
end
|
|
24
56
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
start_monitor(Warden::Monitor.new(config))
|
|
57
|
+
def request_shutdown
|
|
58
|
+
Thread.new do
|
|
59
|
+
shutdown if MUTEX.try_lock
|
|
29
60
|
end
|
|
30
61
|
end
|
|
31
62
|
|
|
32
|
-
def
|
|
33
|
-
|
|
63
|
+
def shutdown
|
|
64
|
+
warn "sending quiet to #{identity}"
|
|
65
|
+
sidekiq_process.quiet!
|
|
66
|
+
|
|
67
|
+
sleep(5)
|
|
68
|
+
|
|
69
|
+
warn "shutting down #{identity} in #{@grace_time} seconds"
|
|
70
|
+
wait_job_finish_in_grace_time
|
|
71
|
+
|
|
72
|
+
warn "stopping #{identity}"
|
|
73
|
+
sidekiq_process.stop!
|
|
74
|
+
|
|
75
|
+
warn "waiting #{@shutdown_wait} seconds before sending #{@kill_signal} to #{identity}"
|
|
76
|
+
sleep(@shutdown_wait)
|
|
77
|
+
|
|
78
|
+
warn "sending #{@kill_signal} to #{identity}"
|
|
79
|
+
::Process.kill(@kill_signal, ::Process.pid)
|
|
34
80
|
end
|
|
35
81
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return if @started
|
|
40
|
-
@started = true
|
|
41
|
-
end
|
|
42
|
-
monitor.start
|
|
82
|
+
def wait_job_finish_in_grace_time
|
|
83
|
+
start = Time.now
|
|
84
|
+
sleep(1) until grace_time_exceeded?(start) || jobs_finished?
|
|
43
85
|
end
|
|
44
86
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@config = config
|
|
48
|
-
@logger = config.logger || Sidekiq.logger
|
|
49
|
-
@triggered = false
|
|
50
|
-
@lock = Mutex.new
|
|
51
|
-
@start_lock = Mutex.new
|
|
52
|
-
@started = false
|
|
53
|
-
end
|
|
87
|
+
def grace_time_exceeded?(start)
|
|
88
|
+
return false if @grace_time == Float::INFINITY
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return if @started
|
|
58
|
-
@started = true
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
@thread = Thread.new do
|
|
62
|
-
Thread.current.name = "sidekiq-mem-warden" if Thread.current.respond_to?(:name=)
|
|
63
|
-
loop do
|
|
64
|
-
sleep @config.check_interval
|
|
65
|
-
next unless over_limit?
|
|
66
|
-
trigger!
|
|
67
|
-
break
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
90
|
+
start + @grace_time < Time.now
|
|
91
|
+
end
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@lock.synchronize do
|
|
76
|
-
return if @triggered
|
|
77
|
-
@triggered = true
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
@logger.warn("[sidekiq-mem-warden] RSS over limit (#{rss_mb}MB >= #{@config.memory_limit_mb}MB), quieting")
|
|
81
|
-
begin
|
|
82
|
-
Process.kill("TSTP", Process.pid)
|
|
83
|
-
rescue StandardError => e
|
|
84
|
-
@logger.warn("[sidekiq-mem-warden] failed to send TSTP: #{e.class}: #{e.message}")
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
sleep @config.quiet_timeout
|
|
88
|
-
wait_for_idle(@config.shutdown_timeout)
|
|
89
|
-
|
|
90
|
-
@logger.warn("[sidekiq-mem-warden] shutting down for restart")
|
|
91
|
-
begin
|
|
92
|
-
Process.kill("TERM", Process.pid)
|
|
93
|
-
rescue StandardError => e
|
|
94
|
-
@logger.warn("[sidekiq-mem-warden] failed to send TERM: #{e.class}: #{e.message}")
|
|
95
|
-
end
|
|
96
|
-
end
|
|
93
|
+
def jobs_finished?
|
|
94
|
+
sidekiq_process.stopping? && sidekiq_process["busy"] == 0
|
|
95
|
+
end
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
busy = Sidekiq::Workers.new.size
|
|
102
|
-
return if busy == 0
|
|
103
|
-
sleep 1
|
|
104
|
-
end
|
|
105
|
-
@logger.warn("[sidekiq-mem-warden] timeout waiting for busy to drain; proceeding")
|
|
106
|
-
end
|
|
97
|
+
def current_rss
|
|
98
|
+
::GetProcessMem.new.mb
|
|
99
|
+
end
|
|
107
100
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
def sidekiq_process
|
|
102
|
+
Sidekiq::ProcessSet.new.find do |process|
|
|
103
|
+
process["identity"] == identity
|
|
104
|
+
end || raise("No sidekiq worker with identity #{identity} found")
|
|
105
|
+
end
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
def identity
|
|
108
|
+
if respond_to?(:config) && config
|
|
109
|
+
config[:identity] || config["identity"]
|
|
110
|
+
else
|
|
111
|
+
Sidekiq.default_configuration[:identity] || Sidekiq.default_configuration["identity"]
|
|
114
112
|
end
|
|
113
|
+
end
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if File.exist?(status_path)
|
|
119
|
-
status = File.read(status_path)
|
|
120
|
-
match = status.match(/^VmRSS:\s+(\d+)\s+kB$/)
|
|
121
|
-
return match[1].to_i if match
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
output = `ps -o rss= -p #{Process.pid}`.to_s.strip
|
|
125
|
-
output.to_i
|
|
126
|
-
rescue StandardError
|
|
127
|
-
0
|
|
128
|
-
end
|
|
115
|
+
def warn(msg)
|
|
116
|
+
Sidekiq.logger.warn(msg)
|
|
129
117
|
end
|
|
130
118
|
end
|
|
131
119
|
end
|
data/sidekiq-mem-warden.gemspec
CHANGED
|
@@ -36,7 +36,8 @@ Gem::Specification.new do |spec|
|
|
|
36
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
37
37
|
spec.require_paths = ["lib"]
|
|
38
38
|
|
|
39
|
-
spec.add_dependency "
|
|
39
|
+
spec.add_dependency "get_process_mem", ">= 0.2.0"
|
|
40
|
+
spec.add_dependency "sidekiq", ">= 7.0"
|
|
40
41
|
spec.add_development_dependency "bundler", ">= 1.17"
|
|
41
42
|
spec.add_development_dependency "rake", ">= 10.0"
|
|
42
43
|
spec.add_development_dependency "rspec", "~> 3.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sidekiq-mem-warden
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tedaford
|
|
@@ -9,20 +9,34 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: get_process_mem
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.2.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.2.0
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: sidekiq
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
15
29
|
requirements:
|
|
16
30
|
- - ">="
|
|
17
31
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
32
|
+
version: '7.0'
|
|
19
33
|
type: :runtime
|
|
20
34
|
prerelease: false
|
|
21
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
36
|
requirements:
|
|
23
37
|
- - ">="
|
|
24
38
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
39
|
+
version: '7.0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: bundler
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|