worker_killer 1.0.4.189871 → 1.0.5.213977

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1481f9bea1f77d274c4155a94bd83c6527ab72eb53fe6958b8036985d714720c
4
- data.tar.gz: 155090caa35e210c9e130f395b00d4b83eab0ec7dcc3aba1a4419e783410c442
3
+ metadata.gz: 2ac9af870733f50ff006c5a31d9c3030342d76ff3208b8e4193a2407ad8f264c
4
+ data.tar.gz: c222a1cada623f95f8668e61d15e4f32fa986fdf799572e1937365919bc651d4
5
5
  SHA512:
6
- metadata.gz: 3b3d7dbef3dc17f218341dace5fca0a1f4c92263ef104b1c1d7f9d1a5f659650e21578f00844f55cd073228117b17744cb5526c388936e90a862e215d9c9f497
7
- data.tar.gz: 7cd8957b9e57ee14f6ae13ff03dbec12369fc45e82f97d4f93b3f378606460254110c37209f7afb53cd2d053a81ccc2a021089386e15dd83bee7a174783e0683
6
+ metadata.gz: 88918e5de623c64691e96fbfc4f34b6eb19669e3f370be011eed996ef6e9a13aee61997b66adfae929e39cb685396557c3802d9a00dcb0ebef0d54e5cf1193fb
7
+ data.tar.gz: bdb4e2fafb1da70554c3b73284538695bfd918b1b516a9a24f477b02056d721b69745f4ecfe6bfde044fb90815fa55f7178c08c33d9cfd283420754d0175ee69
data/README.md CHANGED
@@ -17,6 +17,7 @@ Features:
17
17
 
18
18
  - generic middleware implementation
19
19
  - Phusion Passenger support(through `passenger-config detach-process <PID>`)
20
+ - Puma phased-restart support(through `pumactl phased-restart`)
20
21
  - DelayedJob support
21
22
  - custom reaction hook
22
23
 
@@ -42,14 +43,14 @@ Add these lines to your `config.ru` or `application.rb`. (These lines should be
42
43
 
43
44
  # Max requests per worker
44
45
  middleware.insert_before(
45
- Rack::Sendfile,
46
+ Rack::Runtime,
46
47
  WorkerKiller::Middleware::RequestsLimiter, killer: killer, min: 3072, max: 4096
47
48
  )
48
49
 
49
50
  # Max memory size (RSS) per worker
50
51
  middleware.insert_before(
51
- Rack::Sendfile,
52
- WorkerKiller::Middleware::OOMLimiter, killer: killer, min: 500 * (1024**2), max: 600 * (1024**2)
52
+ Rack::Runtime,
53
+ WorkerKiller::Middleware::OOMLimiter, killer: killer, min: nil, max: 0.5, check_cycle: 16
53
54
  )
54
55
  ```
55
56
 
@@ -74,6 +75,39 @@ Add these lines to your `initializers/delayed_job.rb` or `application.rb`.
74
75
  end
75
76
  ```
76
77
 
78
+ ## Puma Web-server
79
+
80
+ Add these lines to your `puma.rb` AND `application.rb`.
81
+
82
+ ```ruby
83
+ # puma.rb
84
+
85
+ on_worker_boot do |*args|
86
+ require 'worker_killer/killer'
87
+ require 'active_support/notifications'
88
+
89
+ ::ActiveSupport::Notifications.subscribe 'initialize.custom.rails' do |*eargs|
90
+ event = ActiveSupport::Notifications::Event.new(*eargs)
91
+ config = event.payload[:config]
92
+
93
+ killer = ::WorkerKiller::Killer::Puma.new
94
+ config.middleware.insert_before(
95
+ ::Rack::Runtime,
96
+ ::WorkerKiller::Middleware::RequestsLimiter, killer: killer, min: 3072, max: 4096
97
+ )
98
+
99
+ config.middleware.insert_before(
100
+ ::Rack::Runtime,
101
+ ::WorkerKiller::Middleware::OOMLimiter, killer: killer, min: nil, max: 0.5, verbose: true, check_cycle: 16
102
+ )
103
+ end
104
+ end
105
+
106
+ # application.rb
107
+
108
+ ActiveSupport::Notifications.instrument 'initialize.custom.rails', config: config
109
+ ```
110
+
77
111
  This gem provides two modules: WorkerKiller::CountLimiter and WorkerKiller::MemoryLimiter, some Rack integration and DelayedJob plugin.
78
112
 
79
113
  ### WorkerKiller::Middleware::RequestsLimiter and WorkerKiller::DelayedJobPlugin::JobsLimiter
@@ -0,0 +1,62 @@
1
+ require 'socket'
2
+
3
+ module ::WorkerKiller
4
+ module Killer
5
+ class Puma < ::WorkerKiller::Killer::Base
6
+
7
+ attr_accessor :type, :plugin_path, :num
8
+
9
+ def initialize(type: :phased, path: nil, num: nil, **kwargs)
10
+ super(**kwargs)
11
+ @type = type
12
+ @plugin_path = path
13
+ @num = num
14
+ end
15
+
16
+ def do_kill(sig, pid, alive_sec, **params)
17
+ if @type == :phased
18
+ do_phased_kill(sig, pid, alive_sec, **params)
19
+ elsif @type == :plugin
20
+ do_plugin_kill(sig, pid, alive_sec, **params)
21
+ end
22
+ end
23
+
24
+ def do_phased_kill(sig, pid, alive_sec, **_params)
25
+ cmd = 'pumactl phased-restart'
26
+
27
+ if sig == :KILL
28
+ logger.error "#{self} force to kill self (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
29
+ Process.kill sig, pid
30
+ return
31
+ end
32
+
33
+ return if @already_detached
34
+
35
+ logger.warn "#{self} run #{cmd.inspect} (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
36
+ @already_detached = true
37
+
38
+ Thread.new(cmd) do |command|
39
+ unless Kernel.system(command)
40
+ logger.warn "#{self} run #{command.inspect} failed: #{$?.inspect}"
41
+ end
42
+ end
43
+ end
44
+
45
+ def do_plugin_kill(sig, pid, alive_sec, **_params)
46
+ if sig == :KILL
47
+ logger.error "#{self} force to kill self (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
48
+ Process.kill sig, pid
49
+ return
50
+ end
51
+
52
+ logger.warn "#{self} send #{num} to Puma Plugin (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
53
+
54
+ Socket.unix(plugin_path) do |sock|
55
+ sock.puts num.to_s
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+
@@ -38,5 +38,6 @@ end
38
38
 
39
39
  require_relative 'killer/signal'
40
40
  require_relative 'killer/passenger'
41
+ require_relative 'killer/puma'
41
42
  require_relative 'killer/delayed_job'
42
43
 
@@ -0,0 +1,104 @@
1
+ require 'socket'
2
+ require 'puma/plugin'
3
+ require 'active_support/notifications'
4
+
5
+ require 'worker_killer/killer'
6
+
7
+
8
+
9
+
10
+ module WorkerKiller
11
+ module Puma
12
+ module Plugin
13
+ # Puma requires such plugin name and path :(
14
+ class WorkerKiller
15
+
16
+ def initialize(launcher, path)
17
+ @runner = launcher.instance_variable_get('@runner')
18
+
19
+ @thread = Thread.new do
20
+ Socket.unix_server_loop(path) do |sock, _client_addrinfo|
21
+ if (line = sock.gets)
22
+ worker_num = Integer(line.strip)
23
+ if (worker = find_worker(worker_num))
24
+ log "Killing worker #{worker_num}"
25
+ worker.term!
26
+ end
27
+ end
28
+ rescue StandardError => e
29
+ log("Exception: #{e.inspect}")
30
+ ensure
31
+ sock.close
32
+ end
33
+ end
34
+ end
35
+
36
+ def find_worker(worker_num)
37
+ worker = @runner.worker_at(worker_num)
38
+ unless worker
39
+ log "Unknown worker index: #{worker_num.inspect}. Skipping."
40
+ return nil
41
+ end
42
+
43
+ unless worker.booted?
44
+ log "Worker #{worker_num.inspect} is not booted yet. Skipping."
45
+ return nil
46
+ end
47
+
48
+ if worker.term?
49
+ log "Worker #{worker_num.inspect} already terminating. Skipping."
50
+ return nil
51
+ end
52
+
53
+ worker
54
+ end
55
+
56
+ def log(msg)
57
+ warn("#{self.class}: #{msg}")
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ Puma::Plugin.create do
66
+ attr_reader :path
67
+
68
+ def config(c)
69
+ c.on_worker_boot do |num|
70
+
71
+ if @killer
72
+ puts "ON OTHER WORKER BOOT: #{num}"
73
+ @killer.num = num
74
+ else
75
+ puts "ON MAIN WORKER BOOT: #{num}"
76
+ ::ActiveSupport::Notifications.subscribe 'worker_killer.initialize' do |*eargs|
77
+ @killer = ::WorkerKiller::Killer::Puma.new(num: num, path: path, type: :plugin)
78
+ puts "ON WORKER BOOT INIT:#{num}"
79
+ event = ActiveSupport::Notifications::Event.new(*eargs)
80
+ config = event.payload[:config]
81
+
82
+ @register_killer&.call(self, config, @killer)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def start(launcher)
89
+ @path = File.join('tmp', "puma_worker_killer_#{Process.pid}.socket")
90
+
91
+ launcher.events.on_booted do
92
+ @controller ||= WorkerKiller::Puma::Plugin::WorkerKiller.new(launcher, path)
93
+ end
94
+ end
95
+
96
+ def configure(&block)
97
+ self.instance_eval(&block)
98
+ end
99
+
100
+ def register_killer(&block)
101
+ @register_killer = block
102
+ end
103
+ end
104
+
@@ -0,0 +1,58 @@
1
+ require 'worker_killer/memory_limiter'
2
+ require 'worker_killer/count_limiter'
3
+ require 'puma/plugin'
4
+
5
+ Puma::Plugin.create do
6
+
7
+ module WorkerKiller
8
+ class PumaPlugin
9
+
10
+ attr_reader :limiter, :killer, :reaction
11
+
12
+ def initialize(klass:, killer:, reaction: nil, **opts)
13
+ @killer = killer
14
+
15
+ @reaction = reaction || proc do |l, k, dj|
16
+ k.kill(l.started_at, dj: dj)
17
+ end
18
+
19
+ @limiter = klass.new(**opts)
20
+ @time_to_burn = false
21
+ end
22
+
23
+ def new(lifecycle = Delayed::Worker.lifecycle, *_args)
24
+ configure_lifecycle(lifecycle)
25
+ end
26
+
27
+ def configure_lifecycle(lifecycle)
28
+ # Count condition after every job
29
+ lifecycle.after(:perform) do |worker, *_args|
30
+ @time_to_burn ||= limiter.check
31
+ end
32
+
33
+ # Stop execution only after whole loop completed
34
+ lifecycle.after(:loop) do |worker, *_args|
35
+ @time_to_burn ||= limiter.check
36
+ reaction.call(limiter, killer, worker) if @time_to_burn
37
+ end
38
+ end
39
+
40
+ class JobsLimiter < ::WorkerKiller::PumaPlugin
41
+
42
+ def initialize(**opts)
43
+ super(klass: ::WorkerKiller::CountLimiter, **opts)
44
+ end
45
+
46
+ end
47
+
48
+ class OOMLimiter < ::WorkerKiller::PumaPlugin
49
+
50
+ def initialize(**opts)
51
+ super(klass: ::WorkerKiller::MemoryLimiter, **opts)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
58
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  module WorkerKiller
4
4
 
5
- VERSION = '1.0.4'
5
+ VERSION = '1.0.5'
6
6
 
7
7
  end
8
8
 
@@ -0,0 +1,33 @@
1
+ RSpec.describe WorkerKiller::Killer::Puma do
2
+ let(:config) do
3
+ WorkerKiller::Configuration.new.tap do |c|
4
+ c.quit_attempts = 2
5
+ c.term_attempts = 2
6
+ end
7
+ end
8
+
9
+ let(:killer){ described_class.new }
10
+
11
+ describe '#kill' do
12
+ around do |example|
13
+ prev = WorkerKiller.configuration
14
+ WorkerKiller.configuration = config
15
+ example.run
16
+ ensure
17
+ WorkerKiller.configuration = prev
18
+ end
19
+
20
+ it 'expect right signal order' do
21
+ expect(Kernel).to receive(:system).with('pumactl phased-restart').and_return(true)
22
+ expect(Process).to receive(:kill).with(:KILL, anything).exactly(5).times
23
+
24
+ thread = killer.kill(Time.now)
25
+ thread.join
26
+
27
+ 1.times { killer.kill(Time.now) } # 1 QUIT
28
+ 2.times { killer.kill(Time.now) } # 1 TERM
29
+ 5.times { killer.kill(Time.now) } # 5 KILL
30
+ end
31
+ end
32
+ end
33
+
data/spec/killer_spec.rb CHANGED
@@ -19,7 +19,7 @@ RSpec.describe WorkerKiller::Killer::Base do
19
19
  end
20
20
 
21
21
  it 'expect right signal order' do
22
- if RUBY_VERSION >= "3.0.0"
22
+ if RUBY_VERSION >= "2.7.0"
23
23
  expect(killer).to receive(:do_kill).with(:QUIT, anything, anything).exactly(2).times
24
24
  expect(killer).to receive(:do_kill).with(:TERM, anything, anything).exactly(2).times
25
25
  expect(killer).to receive(:do_kill).with(:KILL, anything, anything).exactly(5).times
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: worker_killer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4.189871
4
+ version: 1.0.5.213977
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samoilenko Yuri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-05 00:00:00.000000000 Z
11
+ date: 2024-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: get_process_mem
@@ -131,15 +131,19 @@ files:
131
131
  - lib/worker_killer/killer.rb
132
132
  - lib/worker_killer/killer/delayed_job.rb
133
133
  - lib/worker_killer/killer/passenger.rb
134
+ - lib/worker_killer/killer/puma.rb
134
135
  - lib/worker_killer/killer/signal.rb
135
136
  - lib/worker_killer/memory_limiter.rb
136
137
  - lib/worker_killer/middleware.rb
138
+ - lib/worker_killer/puma/plugin/worker_killer.rb
139
+ - lib/worker_killer/puma_plugin.rb
137
140
  - lib/worker_killer/version.rb
138
141
  - spec/count_limiter_spec.rb
139
142
  - spec/delayed_job_plugin/jobs_limiter_spec.rb
140
143
  - spec/delayed_job_plugin/oom_limiter_spec.rb
141
144
  - spec/killer/delayed_job_spec.rb
142
145
  - spec/killer/passenger_spec.rb
146
+ - spec/killer/puma_spec.rb
143
147
  - spec/killer/signal_spec.rb
144
148
  - spec/killer_spec.rb
145
149
  - spec/memory_limiter_spec.rb
@@ -177,6 +181,7 @@ test_files:
177
181
  - spec/delayed_job_plugin/oom_limiter_spec.rb
178
182
  - spec/delayed_job_plugin/jobs_limiter_spec.rb
179
183
  - spec/killer/passenger_spec.rb
184
+ - spec/killer/puma_spec.rb
180
185
  - spec/killer/signal_spec.rb
181
186
  - spec/killer/delayed_job_spec.rb
182
187
  - spec/memory_limiter_spec.rb