worker_killer 1.0.5.213977 → 1.1.0.214146
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/README.md +39 -23
- data/lib/worker_killer/count_limiter.rb +9 -3
- data/lib/worker_killer/delayed_job_plugin.rb +2 -2
- data/lib/worker_killer/killer/passenger.rb +1 -1
- data/lib/worker_killer/killer/puma.rb +9 -39
- data/lib/worker_killer/killer.rb +2 -2
- data/lib/worker_killer/memory_limiter.rb +11 -4
- data/lib/worker_killer/puma/plugin/worker_killer.rb +5 -96
- data/lib/worker_killer/puma_plugin.rb +53 -31
- data/lib/worker_killer/version.rb +1 -1
- data/spec/count_limiter_spec.rb +20 -11
- data/spec/killer/puma_spec.rb +6 -4
- data/spec/memory_limiter_spec.rb +15 -2
- data/spec/puma_plugin_spec.rb +34 -0
- data/spec/spec_helper.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eabc2f6b314a0e9a0e81a078ac3954c5355e24017373fad26f4de4fbeea2f00f
|
|
4
|
+
data.tar.gz: deb1ebcf899be03574b4611aee0b3f8e08f4c6f57e5babddeaa2bc5d20f70ed7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80da7063d7ab1d3ce70baf42414e7dc6aea1310d85285cc10c95c9a09d980b0b8db98438985ffe422e82b20c4aab73ea05c717924dac47442c4aed1f257a1fae
|
|
7
|
+
data.tar.gz: 0e40fd946bb0a0f5e4cdb36e469738c195c555e4495e379f943a90fb336166f4f56e09a7a60ffafea782a597979db79d21123ded769c4412843d8f05397aa12d
|
data/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Features:
|
|
|
17
17
|
|
|
18
18
|
- generic middleware implementation
|
|
19
19
|
- Phusion Passenger support(through `passenger-config detach-process <PID>`)
|
|
20
|
-
- Puma
|
|
20
|
+
- Puma support through native plugin
|
|
21
21
|
- DelayedJob support
|
|
22
22
|
- custom reaction hook
|
|
23
23
|
|
|
@@ -82,33 +82,26 @@ Add these lines to your `puma.rb` AND `application.rb`.
|
|
|
82
82
|
```ruby
|
|
83
83
|
# puma.rb
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
85
|
+
require 'worker_killer/puma/plugin/worker_killer'
|
|
86
|
+
plugin('worker_killer')
|
|
105
87
|
|
|
106
88
|
# application.rb
|
|
89
|
+
if defined?(::Puma::Client)
|
|
90
|
+
killer = ::WorkerKiller::PumaPlugin.instance.killer
|
|
91
|
+
|
|
92
|
+
config.middleware.insert_before(
|
|
93
|
+
Rack::Runtime,
|
|
94
|
+
WorkerKiller::Middleware::OOMLimiter, killer: killer, min: nil, max: 0.5, verbose: false, check_cycle: 16
|
|
95
|
+
)
|
|
107
96
|
|
|
108
|
-
|
|
97
|
+
config.middleware.insert_before(
|
|
98
|
+
Rack::Runtime,
|
|
99
|
+
WorkerKiller::Middleware::RequestsLimiter, killer: killer, min: 3000, max: 4000, verbose: false
|
|
100
|
+
)
|
|
101
|
+
end
|
|
109
102
|
```
|
|
110
103
|
|
|
111
|
-
This gem provides two modules: WorkerKiller::CountLimiter and WorkerKiller::MemoryLimiter, some Rack integration and
|
|
104
|
+
This gem provides two modules: WorkerKiller::CountLimiter and WorkerKiller::MemoryLimiter, some Rack integration, DelayedJob plugin and Puma plugin.
|
|
112
105
|
|
|
113
106
|
### WorkerKiller::Middleware::RequestsLimiter and WorkerKiller::DelayedJobPlugin::JobsLimiter
|
|
114
107
|
|
|
@@ -128,6 +121,29 @@ The memory size check is done in every `check_cycle` requests.
|
|
|
128
121
|
|
|
129
122
|
If `verbose` is set to true, then every memory size check will be shown in your logs. This logging is done at the `info` level.
|
|
130
123
|
|
|
124
|
+
### WorkerKiller::PumaPlugin
|
|
125
|
+
|
|
126
|
+
Works in Puma `Cluster Mode`. Work correctly with `phased-restart` and `fork_workers`, but has maximum effectivity (memory usage) in simple `Cluster Mode` with **preload_app!**.
|
|
127
|
+
|
|
128
|
+
Example production config:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
port ENV.fetch('PUMA_PORT', 3000).to_i
|
|
132
|
+
|
|
133
|
+
environment ENV.fetch('RAILS_ENV', 'development')
|
|
134
|
+
|
|
135
|
+
pidfile ENV.fetch('PUMA_PIDFILE', 'tmp/pids/server.pid')
|
|
136
|
+
|
|
137
|
+
threads ENV.fetch('PUMA_THREADS', 4).to_i
|
|
138
|
+
|
|
139
|
+
workers ENV.fetch('PUMA_WORKERS', 3).to_i
|
|
140
|
+
|
|
141
|
+
require 'worker_killer/puma/plugin/worker_killer'
|
|
142
|
+
plugin('worker_killer')
|
|
143
|
+
|
|
144
|
+
preload_app!
|
|
145
|
+
```
|
|
146
|
+
|
|
131
147
|
# Special Thanks
|
|
132
148
|
|
|
133
149
|
- [@hotchpotch](http://github.com/hotchpotch/) for the [original idea](https://gist.github.com/hotchpotch/1258681)
|
|
@@ -6,13 +6,19 @@ module WorkerKiller
|
|
|
6
6
|
def initialize(min: 3072, max: 4096, verbose: false)
|
|
7
7
|
@min = min
|
|
8
8
|
@max = max
|
|
9
|
-
@limit =
|
|
10
|
-
@left =
|
|
9
|
+
@limit = nil
|
|
10
|
+
@left = nil
|
|
11
11
|
@verbose = verbose
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def check
|
|
15
|
-
|
|
15
|
+
# initialize limits on first check
|
|
16
|
+
if @limit.nil?
|
|
17
|
+
@limit = min + WorkerKiller.randomize(max - min + 1)
|
|
18
|
+
@left = @limit
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return nil if @limit < 1
|
|
16
22
|
|
|
17
23
|
@started_at ||= Time.now
|
|
18
24
|
|
|
@@ -23,10 +23,10 @@ module WorkerKiller
|
|
|
23
23
|
|
|
24
24
|
def configure_lifecycle(lifecycle)
|
|
25
25
|
# Count condition after every job
|
|
26
|
-
lifecycle.after(:perform) do |
|
|
26
|
+
lifecycle.after(:perform) do |_worker, *_args|
|
|
27
27
|
@time_to_burn ||= limiter.check
|
|
28
28
|
end
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
# Stop execution only after whole loop completed
|
|
31
31
|
lifecycle.after(:loop) do |worker, *_args|
|
|
32
32
|
@time_to_burn ||= limiter.check
|
|
@@ -4,55 +4,25 @@ module ::WorkerKiller
|
|
|
4
4
|
module Killer
|
|
5
5
|
class Puma < ::WorkerKiller::Killer::Base
|
|
6
6
|
|
|
7
|
-
attr_accessor :
|
|
7
|
+
attr_accessor :ipc_path, :worker_num
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
9
|
+
def initialize(ipc_path:, worker_num: nil, **kwargs)
|
|
10
10
|
super(**kwargs)
|
|
11
|
-
@
|
|
12
|
-
@
|
|
13
|
-
@num = num
|
|
11
|
+
@ipc_path = ipc_path
|
|
12
|
+
@worker_num = worker_num
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
def do_kill(sig, pid, alive_sec, **
|
|
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)
|
|
15
|
+
def do_kill(sig, pid, alive_sec, **_params)
|
|
46
16
|
if sig == :KILL
|
|
47
|
-
logger.error "#{self} force to kill self (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
|
|
17
|
+
logger.error "#{self} force to kill self[#{worker_num}] (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
|
|
48
18
|
Process.kill sig, pid
|
|
49
19
|
return
|
|
50
20
|
end
|
|
51
21
|
|
|
52
|
-
logger.warn "#{self} send #{
|
|
22
|
+
logger.warn "#{self} send #{worker_num} to Puma Plugin (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts})"
|
|
53
23
|
|
|
54
|
-
Socket.unix(
|
|
55
|
-
sock.puts
|
|
24
|
+
Socket.unix(ipc_path) do |sock|
|
|
25
|
+
sock.puts Integer(worker_num).to_s
|
|
56
26
|
end
|
|
57
27
|
end
|
|
58
28
|
|
data/lib/worker_killer/killer.rb
CHANGED
|
@@ -2,7 +2,7 @@ module WorkerKiller
|
|
|
2
2
|
module Killer
|
|
3
3
|
class Base
|
|
4
4
|
|
|
5
|
-
attr_accessor :config, :kill_attempts
|
|
5
|
+
attr_accessor :config, :kill_attempts
|
|
6
6
|
|
|
7
7
|
def initialize(logger: nil, **_kwargs)
|
|
8
8
|
@logger = logger
|
|
@@ -27,7 +27,7 @@ module WorkerKiller
|
|
|
27
27
|
raise 'Not Implemented'
|
|
28
28
|
end
|
|
29
29
|
# :nocov:
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
def logger
|
|
32
32
|
@logger || WorkerKiller.configuration.logger
|
|
33
33
|
end
|
|
@@ -10,8 +10,6 @@ module WorkerKiller
|
|
|
10
10
|
# set static memory limits
|
|
11
11
|
@min = min
|
|
12
12
|
@max = max
|
|
13
|
-
@limit = @min + WorkerKiller.randomize(@max - @min + 1)
|
|
14
|
-
@limit_mb = (@limit / 1024 / 1024).round(1)
|
|
15
13
|
else
|
|
16
14
|
# prepare for relative memory limits
|
|
17
15
|
@max_percent = max
|
|
@@ -26,11 +24,19 @@ module WorkerKiller
|
|
|
26
24
|
@started_at ||= Time.now
|
|
27
25
|
@check_count += 1
|
|
28
26
|
|
|
27
|
+
|
|
29
28
|
return nil if (@check_count % @check_cycle) != 0
|
|
30
29
|
|
|
31
30
|
rss = GetProcessMem.new.bytes
|
|
31
|
+
|
|
32
32
|
# initialize relative memory limits on first check
|
|
33
|
-
|
|
33
|
+
if @limit.nil?
|
|
34
|
+
if min.nil?
|
|
35
|
+
set_limits(rss, rss + rss * @max_percent)
|
|
36
|
+
else
|
|
37
|
+
set_limits(min, min + WorkerKiller.randomize(max - min + 1))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
34
40
|
|
|
35
41
|
do_check(rss)
|
|
36
42
|
end
|
|
@@ -52,7 +58,8 @@ module WorkerKiller
|
|
|
52
58
|
|
|
53
59
|
def set_limits(min, max)
|
|
54
60
|
@min = min
|
|
55
|
-
@limit =
|
|
61
|
+
@limit = max
|
|
62
|
+
@max ||= max
|
|
56
63
|
@limit_mb = (@limit / 1024 / 1024).round(1)
|
|
57
64
|
end
|
|
58
65
|
|
|
@@ -1,104 +1,13 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
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
|
|
1
|
+
require 'worker_killer'
|
|
2
|
+
require 'worker_killer/puma_plugin'
|
|
64
3
|
|
|
65
4
|
Puma::Plugin.create do
|
|
66
|
-
|
|
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
|
|
5
|
+
def config(cfg)
|
|
6
|
+
::WorkerKiller::PumaPlugin.instance.config(cfg)
|
|
86
7
|
end
|
|
87
8
|
|
|
88
9
|
def start(launcher)
|
|
89
|
-
|
|
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
|
|
10
|
+
::WorkerKiller::PumaPlugin.instance.start(launcher)
|
|
102
11
|
end
|
|
103
12
|
end
|
|
104
13
|
|
|
@@ -1,56 +1,78 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
|
|
1
3
|
require 'worker_killer/memory_limiter'
|
|
2
4
|
require 'worker_killer/count_limiter'
|
|
3
|
-
require 'puma/plugin'
|
|
4
5
|
|
|
5
|
-
Puma::Plugin.create do
|
|
6
6
|
|
|
7
7
|
module WorkerKiller
|
|
8
8
|
class PumaPlugin
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def initialize(klass:, killer:, reaction: nil, **opts)
|
|
13
|
-
@killer = killer
|
|
10
|
+
include Singleton
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
k.kill(l.started_at, dj: dj)
|
|
17
|
-
end
|
|
12
|
+
attr_accessor :ipc_path, :killer, :thread
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
@
|
|
14
|
+
def initialize
|
|
15
|
+
@ipc_path = File.join('tmp', "puma_worker_killer_#{Process.pid}.socket")
|
|
16
|
+
@killer = ::WorkerKiller::Killer::Puma.new(worker_num: nil, ipc_path: ipc_path)
|
|
17
|
+
log "Initializing IPC: #{@ipc_path}"
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
20
|
+
def config(puma)
|
|
21
|
+
puma.on_worker_boot do |num|
|
|
22
|
+
log "Set worker_num: #{num}"
|
|
23
|
+
@killer.worker_num = num
|
|
37
24
|
end
|
|
38
25
|
end
|
|
39
26
|
|
|
40
|
-
|
|
27
|
+
def start(launcher)
|
|
28
|
+
@runner = launcher.instance_variable_get('@runner')
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
launcher.events.on_booted do
|
|
31
|
+
@thread ||= start_ipc_listener
|
|
44
32
|
end
|
|
33
|
+
end
|
|
45
34
|
|
|
35
|
+
def start_ipc_listener
|
|
36
|
+
log 'Start IPC listener'
|
|
37
|
+
Thread.new do
|
|
38
|
+
Socket.unix_server_loop(ipc_path) do |sock, *args|
|
|
39
|
+
if (line = sock.gets)
|
|
40
|
+
worker_num = Integer(line.strip)
|
|
41
|
+
if (worker = find_worker(worker_num))
|
|
42
|
+
log "Killing worker #{worker_num}"
|
|
43
|
+
worker.term!
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
log("Exception: #{e.inspect}")
|
|
48
|
+
ensure
|
|
49
|
+
sock.close
|
|
50
|
+
end
|
|
51
|
+
end
|
|
46
52
|
end
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
def find_worker(worker_num)
|
|
55
|
+
worker = @runner.worker_at(worker_num)
|
|
56
|
+
unless worker
|
|
57
|
+
log "Unknown worker index: #{worker_num.inspect}. Skipping."
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless worker.booted?
|
|
62
|
+
log "Worker #{worker_num.inspect} is not booted yet. Skipping."
|
|
63
|
+
return nil
|
|
64
|
+
end
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
if worker.term?
|
|
67
|
+
log "Worker #{worker_num.inspect} already terminating. Skipping."
|
|
68
|
+
return nil
|
|
52
69
|
end
|
|
53
70
|
|
|
71
|
+
worker
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def log(msg)
|
|
75
|
+
warn("#{self.class}[#{Process.pid}]: #{msg}")
|
|
54
76
|
end
|
|
55
77
|
|
|
56
78
|
end
|
data/spec/count_limiter_spec.rb
CHANGED
|
@@ -4,21 +4,30 @@ RSpec.describe WorkerKiller::CountLimiter do
|
|
|
4
4
|
let(:max){ min + rand(100) }
|
|
5
5
|
let(:options){ { min: min, max: max, verbose: true } }
|
|
6
6
|
|
|
7
|
-
it { is_expected.to have_attributes(min: min, max: max, limit:
|
|
7
|
+
it { is_expected.to have_attributes(min: min, max: max, limit: nil, left: nil) }
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
context 'initialize limits after first check' do
|
|
10
|
+
before { subject.check }
|
|
11
|
+
|
|
12
|
+
it {
|
|
13
|
+
is_expected.to have_attributes(min: min, max: max,
|
|
14
|
+
limit: a_value_between(min, max), left: subject.limit - 1)
|
|
15
|
+
}
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
it 'expect not to react while less than limit' do
|
|
18
|
+
(subject.limit - 2).times do
|
|
19
|
+
expect(subject.check).to be_falsey
|
|
20
|
+
end
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
expect
|
|
21
|
-
|
|
23
|
+
it 'expect call reaction when check succeded' do
|
|
24
|
+
(subject.limit - 2).times do
|
|
25
|
+
expect(subject.check).to be_falsey
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
expect(subject.check).to be_truthy
|
|
29
|
+
expect(subject.check).to be_truthy
|
|
30
|
+
end
|
|
22
31
|
end
|
|
23
32
|
end
|
|
24
33
|
|
data/spec/killer/puma_spec.rb
CHANGED
|
@@ -6,7 +6,9 @@ RSpec.describe WorkerKiller::Killer::Puma do
|
|
|
6
6
|
end
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
let(:
|
|
9
|
+
let(:ipc_path) { '/tmp/test_ipx.sock' }
|
|
10
|
+
let(:killer){ described_class.new(ipc_path: ipc_path, worker_num: 99) }
|
|
11
|
+
let(:buffer) { StringIO.new }
|
|
10
12
|
|
|
11
13
|
describe '#kill' do
|
|
12
14
|
around do |example|
|
|
@@ -18,11 +20,11 @@ RSpec.describe WorkerKiller::Killer::Puma do
|
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
it 'expect right signal order' do
|
|
21
|
-
expect(
|
|
23
|
+
expect(Socket).to receive(:unix).with(ipc_path).and_yield(buffer).exactly(4).times
|
|
22
24
|
expect(Process).to receive(:kill).with(:KILL, anything).exactly(5).times
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
killer.kill(Time.now)
|
|
27
|
+
expect(buffer.string.strip).to eq(99.to_s)
|
|
26
28
|
|
|
27
29
|
1.times { killer.kill(Time.now) } # 1 QUIT
|
|
28
30
|
2.times { killer.kill(Time.now) } # 1 TERM
|
data/spec/memory_limiter_spec.rb
CHANGED
|
@@ -22,7 +22,7 @@ RSpec.describe WorkerKiller::MemoryLimiter do
|
|
|
22
22
|
let(:min){ rand(50..100) * mb }
|
|
23
23
|
let(:max){ min + rand(100) * mb }
|
|
24
24
|
|
|
25
|
-
it { is_expected.to have_attributes(min: min, max: max, limit:
|
|
25
|
+
it { is_expected.to have_attributes(min: min, max: max, limit: nil) }
|
|
26
26
|
|
|
27
27
|
it 'expect to skip check while less than cycle count' do
|
|
28
28
|
expect(GetProcessMem).not_to receive(:new)
|
|
@@ -30,6 +30,15 @@ RSpec.describe WorkerKiller::MemoryLimiter do
|
|
|
30
30
|
skip_cycles(subject, check_cycle)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
it 'expect to initialize limits after cycle count' do
|
|
34
|
+
expect(memory).to receive(:bytes).and_return(min)
|
|
35
|
+
is_expected.to have_attributes(min: min, max: max, limit: nil)
|
|
36
|
+
|
|
37
|
+
skip_cycles(subject, check_cycle)
|
|
38
|
+
subject.check
|
|
39
|
+
is_expected.to have_attributes(min: min, max: max, limit: a_value_between(min, max))
|
|
40
|
+
end
|
|
41
|
+
|
|
33
42
|
it 'expect to skip check after cycle count reached' do
|
|
34
43
|
expect(memory).to receive(:bytes).and_return(min - 1)
|
|
35
44
|
|
|
@@ -38,8 +47,12 @@ RSpec.describe WorkerKiller::MemoryLimiter do
|
|
|
38
47
|
end
|
|
39
48
|
|
|
40
49
|
it 'expect call reaction when check succeded' do
|
|
41
|
-
expect(memory).to receive(:bytes).and_return(
|
|
50
|
+
expect(memory).to receive(:bytes).and_return(min)
|
|
51
|
+
|
|
52
|
+
skip_cycles(subject, check_cycle)
|
|
53
|
+
subject.check
|
|
42
54
|
|
|
55
|
+
expect(memory).to receive(:bytes).and_return(subject.limit + 1)
|
|
43
56
|
skip_cycles(subject, check_cycle)
|
|
44
57
|
expect(subject.check).to be_truthy
|
|
45
58
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
RSpec.describe WorkerKiller::PumaPlugin do
|
|
2
|
+
subject(:plugin){ described_class.instance }
|
|
3
|
+
let(:puma) { double }
|
|
4
|
+
let(:runner){ double }
|
|
5
|
+
let(:events) { double }
|
|
6
|
+
let(:launcher){ double('@runner' => runner, 'events' => events) }
|
|
7
|
+
let(:worker){ double('booted?' => true, 'term?' => false) }
|
|
8
|
+
|
|
9
|
+
it {
|
|
10
|
+
is_expected.to have_attributes(ipc_path: /puma_worker_.*socket/,
|
|
11
|
+
killer: ::WorkerKiller::Killer::Puma)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
context 'Puma initialization' do
|
|
15
|
+
it do
|
|
16
|
+
expect(puma).to receive(:on_worker_boot)
|
|
17
|
+
plugin.config(puma)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it do
|
|
21
|
+
launcher.instance_variable_set('@runner', runner)
|
|
22
|
+
|
|
23
|
+
expect(Socket).to receive(:unix_server_loop).with(/puma_worker_.*socket/).and_yield(StringIO.new('99'))
|
|
24
|
+
expect(events).to receive(:on_booted).and_yield
|
|
25
|
+
expect(runner).to receive(:worker_at).with(99).and_return(worker)
|
|
26
|
+
expect(plugin).to receive(:find_worker).with(99).and_call_original
|
|
27
|
+
expect(worker).to receive(:term!)
|
|
28
|
+
|
|
29
|
+
plugin.start(launcher)
|
|
30
|
+
plugin.thread.join
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
data/spec/spec_helper.rb
CHANGED
|
@@ -30,6 +30,7 @@ SimpleCov.start
|
|
|
30
30
|
|
|
31
31
|
require 'worker_killer'
|
|
32
32
|
require 'worker_killer/delayed_job_plugin'
|
|
33
|
+
require 'worker_killer/puma_plugin'
|
|
33
34
|
|
|
34
35
|
$root = File.join(File.dirname(__dir__), 'spec')
|
|
35
36
|
Dir[File.join(__dir__, 'support', '**', '*.rb')].sort.each {|f| require f }
|
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
|
+
version: 1.1.0.214146
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samoilenko Yuri
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-02-
|
|
11
|
+
date: 2024-02-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: get_process_mem
|
|
@@ -148,6 +148,7 @@ files:
|
|
|
148
148
|
- spec/killer_spec.rb
|
|
149
149
|
- spec/memory_limiter_spec.rb
|
|
150
150
|
- spec/middleware_spec.rb
|
|
151
|
+
- spec/puma_plugin_spec.rb
|
|
151
152
|
- spec/spec_helper.rb
|
|
152
153
|
- spec/support/logger.rb
|
|
153
154
|
- spec/worker_killer_spec.rb
|
|
@@ -178,6 +179,7 @@ test_files:
|
|
|
178
179
|
- spec/middleware_spec.rb
|
|
179
180
|
- spec/killer_spec.rb
|
|
180
181
|
- spec/spec_helper.rb
|
|
182
|
+
- spec/puma_plugin_spec.rb
|
|
181
183
|
- spec/delayed_job_plugin/oom_limiter_spec.rb
|
|
182
184
|
- spec/delayed_job_plugin/jobs_limiter_spec.rb
|
|
183
185
|
- spec/killer/passenger_spec.rb
|