sidekiq-worker-killer 0.1.0 → 0.2.0

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
  SHA1:
3
- metadata.gz: 8a2cde25a88bee5182a6b202430223277271a183
4
- data.tar.gz: 2c8b3c8908280b14e71417cbde9daef57b33ff71
3
+ metadata.gz: 643766850e2b525cc9458efaeb259b2525654235
4
+ data.tar.gz: 25cc9a461b70258f9ce09ab71fa4d59f48150744
5
5
  SHA512:
6
- metadata.gz: f72ef844066d409eb417d2b0aa89b629994c621d74a1197076562012a420cad9eab04914492d1e2db9f5d06d684732f62f24a52f3f0608e627e74f25ace39e27
7
- data.tar.gz: bac133d8b5a0ba793ab2bf8fa5a2378a2d20b74a06cfe4fe6c522ace3fa9f382701246485c3a87103cff84894f045d4cc8cf069ccc26d4c3c013d203fd5b36f0
6
+ metadata.gz: 13e54d178b6eaf6de4c18bff3cb7bf5a0f583a2a511880514cb0b50c4158a8acf93dad622f3d906304e6b4a581c1d4a9b34a9e100354e0d3984891fb09ce34ec
7
+ data.tar.gz: e538edc1f1d17a2ffcba3121551bde5234375eb1648e98c1790efd973e1f8934131ad3ec9ecc8ace4101e2ec0ca97c62908e04b62b7b3243c6676a3511458d70
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  # sidekiq-worker-killer
3
+ [![Gem Version](https://badge.fury.io/rb/sidekiq-worker-killer.svg)](https://badge.fury.io/rb/sidekiq-worker-killer)
3
4
  [![CircleCI](https://circleci.com/gh/klaxit/sidekiq-worker-killer.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/klaxit/sidekiq-worker-killer)
4
5
 
5
6
  [Sidekiq](https://github.com/mperham/sidekiq) is probably the best background processing framework today. At the same time, memory leaks are very hard to tackle in Ruby and we often find ourselves with growing memory consumption.
@@ -9,7 +10,7 @@ Highly inspired by [Gitlab Sidekiq MemoryKiller](https://gitlab.com/gitlab-org/g
9
10
  ## Install
10
11
  Use [Bundler](http://bundler.io/)
11
12
  ```
12
- gem "sidekiq-worker-killer", git: "https://github.com/klaxit/sidekiq-worker-killer"
13
+ gem "sidekiq-worker-killer"
13
14
  ```
14
15
 
15
16
  ## Usage
@@ -19,7 +20,7 @@ Add this to your Sidekiq configuration.
19
20
  ```
20
21
  Sidekiq.configure_server do |config|
21
22
  config.server_middleware do |chain|
22
- chain.add Sidekiq::WorkerKiller, max_rss: 250
23
+ chain.add Sidekiq::WorkerKiller, max_rss: 480
23
24
  end
24
25
  end
25
26
  ```
@@ -30,10 +31,10 @@ The following options can be overrided.
30
31
 
31
32
  | Option | Defaults | Description |
32
33
  | ------- | ------- | ----------- |
33
- | max_rss | 0 MB (disabled) | Max RSS in megabytes. |
34
- | grace_time | 900 seconds | When a shutdown is triggered, the Sidekiq process will keep working normally for another 15 minutes. |
35
- | shutdown_wait | 30 seconds | When the grace time expires, existing jobs get 30 seconds to finish. After that, shutdown signal is triggered. |
36
- | shutdown_signal | SIGKILL | Signal to use to shutdown sidekiq |
34
+ | max_rss | 0 MB (disabled) | max RSS in megabytes. Above this, shutdown will be triggered. |
35
+ | grace_time | 900 seconds | when shutdown is triggered, the Sidekiq process will not accept new job but wait 15 minutes for running jobs to finish. |
36
+ | shutdown_wait | 30 seconds | when the grace time expires, still running jobs get 30 seconds to terminate. After that, kill signal is triggered. |
37
+ | kill_signal | SIGKILL | Signal to use kill Sidekiq process if it doesn't terminate. |
37
38
 
38
39
  ## Authors
39
40
 
@@ -1,66 +1,83 @@
1
1
  require "get_process_mem"
2
2
  require "sidekiq"
3
+ require "sidekiq/util"
3
4
 
4
5
  module Sidekiq
5
6
  # Sidekiq server middleware. Kill worker when the RSS memory exceeds limit
6
7
  # after a given grace time.
7
8
  class WorkerKiller
9
+ include Sidekiq::Util
10
+
8
11
  MUTEX = Mutex.new
9
12
 
10
13
  def initialize(options = {})
11
14
  @max_rss = (options[:max_rss] || 0)
12
15
  @grace_time = (options[:grace_time] || 15 * 60)
13
16
  @shutdown_wait = (options[:shutdown_wait] || 30)
14
- @shutdown_signal = (options[:shutdown_signal] || "SIGKILL")
17
+ @kill_signal = (options[:kill_signal] || "SIGKILL")
15
18
  end
16
19
 
17
- def call(worker, _job, _queue)
20
+ def call(_worker, _job, _queue)
18
21
  yield
19
22
  # Skip if the max RSS is not exceeded
20
- Sidekiq.logger.debug("current RSS is #{current_rss} MB")
21
23
  return unless @max_rss > 0 && current_rss > @max_rss
22
- # Perform kill
23
- perform_kill(worker)
24
+ # Launch the shutdown process
25
+ warn "current RSS #{current_rss} of #{identity} exceeds " \
26
+ "maximum RSS #{@max_rss}"
27
+ request_shutdown
24
28
  end
25
29
 
26
30
  private
27
31
 
28
- def perform_kill(worker)
32
+ def request_shutdown
29
33
  # In another thread to allow undelying job to finish
30
34
  Thread.new do
31
- # Return if another thread is already waiting to shut Sidekiq down
32
- return unless MUTEX.try_lock
35
+ # Only if another thread is not already
36
+ # shutting down the Sidekiq process
37
+ shutdown if MUTEX.try_lock
38
+ end
39
+ end
33
40
 
34
- # Perform the killing process
35
- worker_ref = "[PID #{pid} - Worker #{worker.class}]"
41
+ def shutdown
42
+ warn "sending #{quiet_signal} to #{identity}"
43
+ signal(quiet_signal, pid)
36
44
 
37
- warn "current RSS #{current_rss} exceeds maximum RSS #{@max_rss}"
38
- warn "this thread will shut down #{worker_ref} in " \
39
- "#{@grace_time} seconds"
40
- sleep(@grace_time)
45
+ warn "shutting down #{identity} in #{@grace_time} seconds"
46
+ sleep(@grace_time)
41
47
 
42
- warn "sending SIGTERM to #{worker_ref}"
43
- kill("SIGTERM", pid)
48
+ warn "sending SIGTERM to #{identity}"
49
+ signal("SIGTERM", pid)
44
50
 
45
- warn "waiting #{@shutdown_wait} seconds before sending " \
46
- "#{@shutdown_signal} to #{worker_ref}"
47
- sleep(@shutdown_wait)
51
+ warn "waiting #{@shutdown_wait} seconds before sending " \
52
+ "#{@kill_signal} to #{identity}"
53
+ sleep(@kill_signal)
48
54
 
49
- warn "sending #{@shutdown_signal} to #{worker_ref}"
50
- kill(@shutdown_signal, pid)
51
- end
55
+ warn "sending #{@kill_signal} to #{identity}"
56
+ signal(@kill_signal, pid)
52
57
  end
53
58
 
54
59
  def current_rss
55
- @current_rss ||= ::GetProcessMem.new.mb
60
+ ::GetProcessMem.new.mb
56
61
  end
57
62
 
58
- def kill(signal, pid)
63
+ def signal(signal, pid)
59
64
  ::Process.kill(signal, pid)
60
65
  end
61
66
 
62
67
  def pid
63
- @pid ||= ::Process.pid
68
+ ::Process.pid
69
+ end
70
+
71
+ def identity
72
+ "#{hostname}:#{pid}"
73
+ end
74
+
75
+ def quiet_signal
76
+ if Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("5.0")
77
+ "TSTP"
78
+ else
79
+ "USR1"
80
+ end
64
81
  end
65
82
 
66
83
  def warn(msg)
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  class WorkerKiller
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "0.2.0".freeze
4
4
  end
5
5
  end
@@ -1,6 +1,11 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Sidekiq::WorkerKiller do
4
+
5
+ before do
6
+ allow(subject).to receive(:warn) # silence "warn" logs
7
+ end
8
+
4
9
  describe "#call" do
5
10
  let(:worker){ double("worker") }
6
11
  let(:job){ double("job") }
@@ -15,18 +20,44 @@ describe Sidekiq::WorkerKiller do
15
20
  before do
16
21
  allow(subject).to receive(:current_rss).and_return(3)
17
22
  end
18
- it "should perform kill" do
19
- expect(subject).to receive(:perform_kill).with(worker)
23
+ it "should request shutdown" do
24
+ expect(subject).to receive(:request_shutdown)
20
25
  subject.call(worker, job, queue){}
21
26
  end
22
27
  context "but max rss is 0" do
23
28
  subject{ described_class.new(max_rss: 0) }
24
- it "should not perform kill" do
25
- expect(subject).to_not receive(:perform_kill).with(worker)
29
+ it "should not request shutdown" do
30
+ expect(subject).to_not receive(:request_shutdown)
26
31
  subject.call(worker, job, queue){}
27
32
  end
28
33
  end
29
34
  end
35
+ end
36
+
37
+ describe "#request_shutdown" do
38
+ before { allow(subject).to receive(:shutdown){ sleep 0.01 } }
39
+ it "should call shutdown" do
40
+ expect(subject).to receive(:shutdown)
41
+ subject.send(:request_shutdown).join
42
+ end
43
+ it "should not call shutdown twice when called concurrently" do
44
+ expect(subject).to receive(:shutdown).once
45
+ 2.times.map{ subject.send(:request_shutdown) }.each(&:join)
46
+ end
47
+ end
30
48
 
49
+ describe "#quiet_signal" do
50
+ it "should give TSTP if Sidekiq version is > 5.0" do
51
+ stub_const("Sidekiq::VERSION", "5.0")
52
+ expect(subject.send :quiet_signal).to eq "TSTP"
53
+ stub_const("Sidekiq::VERSION", "5.2.1")
54
+ expect(subject.send :quiet_signal).to eq "TSTP"
55
+ end
56
+ it "should give USR1 if Sidekiq version is < 5.0" do
57
+ stub_const("Sidekiq::VERSION", "3.0")
58
+ expect(subject.send :quiet_signal).to eq "USR1"
59
+ stub_const("Sidekiq::VERSION", "4.6.7")
60
+ expect(subject.send :quiet_signal).to eq "USR1"
61
+ end
31
62
  end
32
63
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-worker-killer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyrille Courtiere
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-17 00:00:00.000000000 Z
11
+ date: 2018-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: get_process_mem