puma_worker_killer 0.0.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/check_changelog.yml +12 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +24 -0
- data/.travis.yml +15 -7
- data/CHANGELOG.md +26 -0
- data/Gemfile +5 -1
- data/README.md +126 -28
- data/Rakefile +2 -2
- data/lib/puma_worker_killer.rb +15 -7
- data/lib/puma_worker_killer/auto_reap.rb +3 -2
- data/lib/puma_worker_killer/puma_memory.rb +19 -12
- data/lib/puma_worker_killer/reaper.rb +27 -7
- data/lib/puma_worker_killer/rolling_restart.rb +13 -5
- data/lib/puma_worker_killer/version.rb +3 -1
- data/puma_worker_killer.gemspec +19 -18
- data/test/fixtures/big.ru +12 -0
- data/test/fixtures/config/puma_worker_killer_start.rb +8 -0
- data/test/fixtures/default.ru +7 -0
- data/test/fixtures/{app.ru → fixture_helper.rb} +6 -6
- data/test/fixtures/on_calculation.ru +10 -0
- data/test/fixtures/pre_term.ru +10 -0
- data/test/fixtures/rolling_pre_term.ru +10 -0
- data/test/fixtures/rolling_restart.ru +7 -0
- data/test/puma_worker_killer_test.rb +80 -53
- data/test/test_helper.rb +4 -64
- metadata +55 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 656a49cbbcbc36e3735d20ade22027cc209213f029e51faa7f0f08c7dbcffb70
|
4
|
+
data.tar.gz: 5a328323d77f9ede2a44ab0c37577b6277a682c73ce1ad0f1bf167efb720a62d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f77c25f16200fc313b7d230f852fcd2543f1f2ff1f40863c757c9288cbbc520c6b44f9ba3c51fef26693e1d233b66519100d0443b3020402f55ebee397babd09
|
7
|
+
data.tar.gz: df97c81b1e0d93a8ace45cd1b24a9e6de4fb2f94a85b181650a163f458aae754a6571b5b34721b6b718649059305c2637f0766597b0560f9412a57c56e91281a
|
@@ -0,0 +1,12 @@
|
|
1
|
+
name: Check Changelog
|
2
|
+
|
3
|
+
on: [pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v1
|
10
|
+
- name: Check that CHANGELOG is touched
|
11
|
+
run: |
|
12
|
+
cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
Naming/AccessorMethodName:
|
4
|
+
Enabled: false
|
5
|
+
|
6
|
+
Style/Documentation:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Style/HashSyntax:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Style/ModuleFunction:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Style/StringLiterals:
|
16
|
+
EnforcedStyle: single_quotes
|
17
|
+
|
18
|
+
Style/StringLiteralsInInterpolation:
|
19
|
+
EnforcedStyle: double_quotes
|
20
|
+
|
21
|
+
Gemspec/RequiredRubyVersion:
|
22
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2020-04-03 13:12:53 +0200 using RuboCop version 0.81.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 1
|
10
|
+
# Configuration parameters: IgnoredMethods.
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Max: 21
|
13
|
+
|
14
|
+
# Offense count: 1
|
15
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 11
|
18
|
+
|
19
|
+
# Offense count: 32
|
20
|
+
# Cop supports --auto-correct.
|
21
|
+
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
22
|
+
# URISchemes: http, https
|
23
|
+
Layout/LineLength:
|
24
|
+
Max: 252
|
data/.travis.yml
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
-
|
4
|
-
- 2.
|
5
|
-
- 2.
|
3
|
+
- 2.3.8
|
4
|
+
- 2.4.5
|
5
|
+
- 2.5.3
|
6
|
+
- 2.6.0
|
6
7
|
- ruby-head
|
7
|
-
-
|
8
|
-
|
8
|
+
- rbx
|
9
|
+
before_install:
|
10
|
+
- gem install bundler -v 1.12.5
|
9
11
|
|
10
12
|
matrix:
|
11
13
|
allow_failures:
|
12
14
|
- rvm: ruby-head
|
13
|
-
- rvm: rbx
|
14
|
-
|
15
|
+
- rvm: rbx
|
16
|
+
|
17
|
+
install:
|
18
|
+
- bundle install
|
19
|
+
|
20
|
+
script:
|
21
|
+
- bundle exec rubocop
|
22
|
+
- bundle exec rake test
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
## Master
|
2
|
+
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
- Simplify workers memory calculation in PumaMemory‘s `get_total` method #81
|
6
|
+
- Add rubocop in gemspec and CI, with offenses corrected and unnecessary cops disabled.
|
7
|
+
- Add `pre_term`-like `rolling_pre_term` config for terminations caused by rolling restart (#86)
|
8
|
+
- Fix compatibility with ruby version 2.3.X (#87)
|
9
|
+
|
10
|
+
## 0.1.1
|
11
|
+
|
12
|
+
- Allow PWK to be used with Puma 4 (#72)
|
13
|
+
|
14
|
+
## 0.1.0
|
15
|
+
|
16
|
+
- Emit extra data via `pre_term` callback before puma worker killer terminates a worker #49.
|
17
|
+
|
18
|
+
## 0.0.7
|
19
|
+
|
20
|
+
- Logging is configurable #41
|
21
|
+
|
22
|
+
## 0.0.6
|
23
|
+
|
24
|
+
- Log PID of worker insead of inspecting the worker #33
|
25
|
+
|
1
26
|
## 0.0.5
|
2
27
|
|
3
28
|
- Support for Puma 3.x
|
@@ -9,3 +34,4 @@
|
|
9
34
|
## 0.0.3
|
10
35
|
|
11
36
|
- Fix memory metrics in on linux
|
37
|
+
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,24 @@
|
|
1
1
|
# Puma Worker Killer
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/schneems/puma_worker_killer.png?branch=master)](https://travis-ci.org/schneems/puma_worker_killer)
|
4
|
+
[![Help Contribute to Open Source](https://www.codetriage.com/schneems/puma_worker_killer/badges/users.svg)](https://www.codetriage.com/schneems/puma_worker_killer)
|
4
5
|
|
6
|
+
## !!!!!!!!!!!!!!!! STOP !!!!!!!!!!!!!!!!
|
7
|
+
|
8
|
+
Before you use this gem, know that it is dangerous. If you have a memory issue, you need to fix the issue. The original idea behind this gem is that it would act as a temporary band-aid to buy you time to allow you to fix your issues. If you turn this on and don't fix the underlying memory problems, then things will only get worse over time.
|
9
|
+
|
10
|
+
This gem can also make your performance WORSE. When a worker is killed, and comes back it takes CPU cycles and time. If you are frequently restarting your workers then you're killing your performance.
|
11
|
+
|
12
|
+
Here are some places to start improving your understanding of memory behvior in Ruby:
|
13
|
+
|
14
|
+
- [Complete Guide to Rails Performance (Book)](https://www.railsspeed.com)
|
15
|
+
- [How Ruby uses Memory](https://www.sitepoint.com/ruby-uses-memory/)
|
16
|
+
- [Ruby Memory Use (Heroku Devcenter article I maintain)](https://devcenter.heroku.com/articles/ruby-memory-use)
|
17
|
+
- [Jumping off the Ruby Memory Cliff](https://www.schneems.com/2017/04/12/jumping-off-the-memory-cliff/)
|
18
|
+
- [How Ruby uses memory (Talk)](https://www.schneems.com/2015/05/11/how-ruby-uses-memory.html) (you can skip the first story in the video, the rest are about memory)
|
19
|
+
- [Debugging a memory leak on Heroku](https://blog.codeship.com/debugging-a-memory-leak-on-heroku/)
|
20
|
+
|
21
|
+
If you still need this gem, proceed with caution.
|
5
22
|
|
6
23
|
## What
|
7
24
|
|
@@ -9,7 +26,7 @@ If you have a memory leak in your code, finding and plugging it can be a hercule
|
|
9
26
|
|
10
27
|
Puma worker killer can only function if you have enabled cluster mode or hybrid mode (threads + worker cluster). If you are only using threads (and not workers) then puma worker killer cannot help keep your memory in control.
|
11
28
|
|
12
|
-
BTW restarting your processes to
|
29
|
+
BTW restarting your processes to control memory is like putting a bandaid on a gunshot wound, try figuring out the reason you're seeing so much memory bloat [derailed benchmarks](https://github.com/schneems/derailed_benchmarks) can help.
|
13
30
|
|
14
31
|
|
15
32
|
## Install
|
@@ -22,21 +39,69 @@ gem 'puma_worker_killer'
|
|
22
39
|
|
23
40
|
Then run `$ bundle install`
|
24
41
|
|
25
|
-
|
26
|
-
## Use
|
42
|
+
## Turn on Rolling Restarts - Heroku Mode
|
27
43
|
|
28
|
-
|
44
|
+
A rolling restart will kill each of your workers on a rolling basis. You set the frequency which it conducts the restart. This is a simple way to keep memory down as Ruby web programs generally increase memory usage over time. If you're using Heroku [it is difficult to measure RAM from inside of a container accurately](https://github.com/schneems/get_process_mem/issues/7), so it is recommended to use this feature or use a [log-drain-based worker killer](https://github.com/arches/whacamole). You can enable roling restarts by running:
|
29
45
|
|
30
|
-
|
46
|
+
```ruby
|
47
|
+
# config/puma.rb
|
48
|
+
|
49
|
+
before_fork do
|
50
|
+
require 'puma_worker_killer'
|
31
51
|
|
32
|
-
|
52
|
+
PumaWorkerKiller.enable_rolling_restart # Default is every 6 hours
|
53
|
+
end
|
54
|
+
|
55
|
+
```
|
56
|
+
|
57
|
+
or you can pass in the restart frequency:
|
33
58
|
|
34
59
|
```ruby
|
35
|
-
#
|
36
|
-
|
60
|
+
PumaWorkerKiller.enable_rolling_restart(12 * 3600) # 12 hours in seconds
|
61
|
+
```
|
62
|
+
|
63
|
+
Make sure if you do this to not accidentally call `PumaWorkerKiller.start` as well.
|
64
|
+
|
65
|
+
## Enable Worker Killing
|
66
|
+
|
67
|
+
If you're not running on a containerized platform (like Heroku or Docker) you can try to detect the amount of memory you're using and only kill Puma workers when you're over that limit. It may allow you to go for longer periods of time without killing a worker however it is more error prone than rolling restarts. To enable measurement based worker killing put this in your `config/puma.rb`:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# config/puma.rb
|
71
|
+
|
72
|
+
before_fork do
|
73
|
+
require 'puma_worker_killer'
|
74
|
+
|
75
|
+
PumaWorkerKiller.start
|
76
|
+
end
|
37
77
|
```
|
38
78
|
|
39
|
-
That's it. Now on a regular basis the size of all Puma and all of it's forked processes will be evaluated and if they're over the RAM threshold will be killed. Don't worry Puma will notice a process is missing
|
79
|
+
That's it. Now on a regular basis the size of all Puma and all of it's forked processes will be evaluated and if they're over the RAM threshold will be killed. Don't worry Puma will notice a process is missing and spawn a fresh copy with a much smaller RAM footprint ASAP.
|
80
|
+
|
81
|
+
## Troubleshooting
|
82
|
+
|
83
|
+
When you boot your program locally you should see debug output:
|
84
|
+
|
85
|
+
```
|
86
|
+
[77773] Puma starting in cluster mode...
|
87
|
+
[77773] * Version 3.1.0 (ruby 2.3.1-p112), codename: El Niño Winter Wonderland
|
88
|
+
[77773] * Min threads: 0, max threads: 16
|
89
|
+
[77773] * Environment: development
|
90
|
+
[77773] * Process workers: 2
|
91
|
+
[77773] * Phased restart available
|
92
|
+
[77773] * Listening on tcp://0.0.0.0:9292
|
93
|
+
[77773] Use Ctrl-C to stop
|
94
|
+
[77773] PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.
|
95
|
+
```
|
96
|
+
|
97
|
+
If you don't see any `PumaWorkerKiller` output, make sure that you are running with multiple workers. PWK only functions if you have workers enabled, you should see something like this when Puma boots:
|
98
|
+
|
99
|
+
```
|
100
|
+
[77773] * Process workers: 2
|
101
|
+
```
|
102
|
+
|
103
|
+
If you've configured PWK's frequency try reducing it to a very low value
|
104
|
+
|
40
105
|
|
41
106
|
## Configure
|
42
107
|
|
@@ -47,21 +112,63 @@ PumaWorkerKiller.config do |config|
|
|
47
112
|
config.ram = 1024 # mb
|
48
113
|
config.frequency = 5 # seconds
|
49
114
|
config.percent_usage = 0.98
|
50
|
-
config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds
|
115
|
+
config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds, or 12.hours if using Rails
|
116
|
+
config.reaper_status_logs = true # setting this to false will not log lines like:
|
117
|
+
# PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.
|
118
|
+
|
119
|
+
config.pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed" }
|
120
|
+
config.rolling_pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed by rolling restart" }
|
51
121
|
end
|
52
122
|
PumaWorkerKiller.start
|
53
123
|
```
|
54
124
|
|
55
|
-
|
56
|
-
|
57
|
-
|
125
|
+
### pre_term
|
126
|
+
|
127
|
+
`config.pre_term` will be called just prior to worker termination with the worker that is about to be terminated. This may be useful to use in keeping track of metrics, time of day workers are restarted, etc.
|
128
|
+
|
129
|
+
By default Puma Worker Killer will emit a log when a worker is being killed
|
130
|
+
|
131
|
+
```
|
132
|
+
PumaWorkerKiller: Out of memory. 5 workers consuming total: 500 mb out of max: 450 mb. Sending TERM to pid 23 consuming 53 mb.
|
133
|
+
```
|
134
|
+
|
135
|
+
or
|
136
|
+
|
137
|
+
```
|
138
|
+
PumaWorkerKiller: Rolling Restart. 5 workers consuming total: 650mb mb. Sending TERM to pid 34.
|
139
|
+
```
|
140
|
+
|
141
|
+
However you may want to collect more data, such as sending an event to an error collection service like rollbar or airbrake. The `pre_term` lambda gets called before any worker is killed by PWK for any reason.
|
142
|
+
|
143
|
+
### rolling_pre_term
|
144
|
+
|
145
|
+
`config.rolling_pre_term` will be called just prior to worker termination by rolling restart when rolling restart is enabled.
|
146
|
+
|
147
|
+
It is similar to `config.pre_term`.
|
148
|
+
|
149
|
+
Difference:
|
150
|
+
|
151
|
+
- `config.pre_term` is triggered only by terminations related with exceeding RAM
|
152
|
+
- `config.rolling_pre_term` is triggered only by terminations caused by enabled rolling restart
|
153
|
+
|
154
|
+
### on_calculation
|
155
|
+
|
156
|
+
`config.on_calculation` will be called every time Puma Worker Killer calculates memory usage (`config.frequency`). This may be useful for monitoring your total puma application memory usage, which can be contrasted with other application monitoring solutions.
|
157
|
+
|
158
|
+
This callback lambda is given a single value for the amount of memory used.
|
159
|
+
|
160
|
+
## Attention
|
161
|
+
|
162
|
+
If you start puma as a daemon, to add puma worker killer config into puma config file, rather than into initializers:
|
163
|
+
Sample like this: (in `config/puma.rb` file):
|
164
|
+
|
58
165
|
```ruby
|
59
166
|
before_fork do
|
60
167
|
PumaWorkerKiller.config do |config|
|
61
168
|
config.ram = 1024 # mb
|
62
169
|
config.frequency = 5 # seconds
|
63
170
|
config.percent_usage = 0.98
|
64
|
-
config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds
|
171
|
+
config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds, or 12.hours if using Rails
|
65
172
|
end
|
66
173
|
PumaWorkerKiller.start
|
67
174
|
end
|
@@ -88,32 +195,23 @@ PumaWorkerKiller.frequency = 20 # seconds
|
|
88
195
|
You may want to periodically restart all of your workers rather than simply killing your largest. To do that set:
|
89
196
|
|
90
197
|
```ruby
|
91
|
-
PumaWorkerKiller.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds
|
198
|
+
PumaWorkerKiller.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds, or 12.hours if using Rails
|
92
199
|
```
|
93
200
|
|
94
|
-
By default PumaWorkerKiller will perform a rolling restart of all your worker processes every
|
201
|
+
By default PumaWorkerKiller will perform a rolling restart of all your worker processes every 6 hours. To disable, set to `false`.
|
95
202
|
|
96
|
-
|
97
|
-
|
98
|
-
If you're running on a platform like [Heroku where it is difficult to measure RAM from inside of a container accurately](https://github.com/schneems/get_process_mem/issues/7), you may want to disable the "worker killer" functionality and only use the rolling restart. You can do that by running:
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
PumaWorkerKiller.enable_rolling_restart
|
102
|
-
```
|
103
|
-
|
104
|
-
or you can pass in the restart frequency
|
203
|
+
You may want to hide the following log lines: `PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.`. To do that set:
|
105
204
|
|
106
205
|
```ruby
|
107
|
-
PumaWorkerKiller.
|
206
|
+
PumaWorkerKiller.reaper_status_logs = false
|
108
207
|
```
|
109
208
|
|
110
|
-
|
209
|
+
Note: It is `true` by default.
|
111
210
|
|
112
211
|
## License
|
113
212
|
|
114
213
|
MIT
|
115
214
|
|
116
|
-
|
117
215
|
## Feedback
|
118
216
|
|
119
217
|
Open up an issue or ping me on twitter [@schneems](http://twitter.com/schneems).
|
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
|
@@ -7,7 +7,7 @@ require 'rake/testtask'
|
|
7
7
|
|
8
8
|
task :default => [:test]
|
9
9
|
|
10
|
-
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
11
|
t.libs << 'lib'
|
12
12
|
t.libs << 'test'
|
13
13
|
t.pattern = 'test/**/*_test.rb'
|
data/lib/puma_worker_killer.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'get_process_mem'
|
2
4
|
|
3
5
|
module PumaWorkerKiller
|
4
6
|
extend self
|
5
7
|
|
6
|
-
attr_accessor :ram, :frequency, :percent_usage, :rolling_restart_frequency
|
8
|
+
attr_accessor :ram, :frequency, :percent_usage, :rolling_restart_frequency,
|
9
|
+
:reaper_status_logs, :pre_term, :rolling_pre_term, :on_calculation
|
10
|
+
|
7
11
|
self.ram = 512 # mb
|
8
12
|
self.frequency = 10 # seconds
|
9
13
|
self.percent_usage = 0.99 # percent of RAM to use
|
10
|
-
self.rolling_restart_frequency = 6 * 3600
|
14
|
+
self.rolling_restart_frequency = 6 * 3600 # 6 hours in seconds
|
15
|
+
self.reaper_status_logs = true
|
16
|
+
self.pre_term = nil
|
17
|
+
self.rolling_pre_term = nil
|
18
|
+
self.on_calculation = nil
|
11
19
|
|
12
20
|
def config
|
13
21
|
yield self
|
14
22
|
end
|
15
23
|
|
16
|
-
def reaper(ram = self.ram,
|
17
|
-
Reaper.new(ram * percent_usage)
|
24
|
+
def reaper(ram = self.ram, percent_usage = self.percent_usage, reaper_status_logs = self.reaper_status_logs, pre_term = self.pre_term, on_calculation = self.on_calculation)
|
25
|
+
Reaper.new(ram * percent_usage, nil, reaper_status_logs, pre_term, on_calculation)
|
18
26
|
end
|
19
27
|
|
20
28
|
def start(frequency = self.frequency, reaper = self.reaper)
|
@@ -22,9 +30,9 @@ module PumaWorkerKiller
|
|
22
30
|
enable_rolling_restart(rolling_restart_frequency) if rolling_restart_frequency
|
23
31
|
end
|
24
32
|
|
25
|
-
def enable_rolling_restart(frequency =
|
26
|
-
frequency
|
27
|
-
AutoReap.new(frequency, RollingRestart.new).start
|
33
|
+
def enable_rolling_restart(frequency = rolling_restart_frequency)
|
34
|
+
frequency += rand(0..10.0) # so all workers don't restart at the exact same time across multiple machines
|
35
|
+
AutoReap.new(frequency, RollingRestart.new(nil, rolling_pre_term)).start
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PumaWorkerKiller
|
2
4
|
class AutoReap
|
3
5
|
def initialize(timeout, reaper = Reaper.new)
|
@@ -11,11 +13,10 @@ module PumaWorkerKiller
|
|
11
13
|
|
12
14
|
Thread.new do
|
13
15
|
while @running
|
14
|
-
@reaper.reap
|
15
16
|
sleep @timeout
|
17
|
+
@reaper.reap
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
19
|
-
|
20
21
|
end
|
21
22
|
end
|
@@ -1,21 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PumaWorkerKiller
|
2
4
|
class PumaMemory
|
3
5
|
def initialize(master = nil)
|
4
6
|
@master = master || get_master
|
7
|
+
@workers = nil
|
5
8
|
end
|
6
9
|
|
7
|
-
|
8
|
-
@master
|
9
|
-
end
|
10
|
+
attr_reader :master
|
10
11
|
|
11
12
|
def size
|
12
13
|
workers.size
|
13
14
|
end
|
14
15
|
|
16
|
+
def term_worker(worker)
|
17
|
+
worker.term
|
18
|
+
end
|
19
|
+
|
15
20
|
def term_largest_worker
|
16
21
|
largest_worker.term
|
17
|
-
|
18
|
-
|
22
|
+
end
|
23
|
+
|
24
|
+
def workers_stopped?
|
25
|
+
!running?
|
19
26
|
end
|
20
27
|
|
21
28
|
def running?
|
@@ -23,7 +30,7 @@ module PumaWorkerKiller
|
|
23
30
|
end
|
24
31
|
|
25
32
|
def smallest_worker
|
26
|
-
smallest,
|
33
|
+
smallest, = workers.to_a.first
|
27
34
|
smallest
|
28
35
|
end
|
29
36
|
|
@@ -33,7 +40,7 @@ module PumaWorkerKiller
|
|
33
40
|
end
|
34
41
|
|
35
42
|
def largest_worker
|
36
|
-
largest_worker,
|
43
|
+
largest_worker, = workers.to_a.last
|
37
44
|
largest_worker
|
38
45
|
end
|
39
46
|
|
@@ -45,10 +52,10 @@ module PumaWorkerKiller
|
|
45
52
|
# Will refresh @workers
|
46
53
|
def get_total(workers = set_workers)
|
47
54
|
master_memory = GetProcessMem.new(Process.pid).mb
|
48
|
-
worker_memory = workers.
|
55
|
+
worker_memory = workers.values.inject(:+) || 0
|
49
56
|
worker_memory + master_memory
|
50
57
|
end
|
51
|
-
alias
|
58
|
+
alias get_total_memory get_total
|
52
59
|
|
53
60
|
def workers
|
54
61
|
@workers || set_workers
|
@@ -64,14 +71,14 @@ module PumaWorkerKiller
|
|
64
71
|
# sorted by memory ascending (smallest first, largest last)
|
65
72
|
def set_workers
|
66
73
|
workers = {}
|
67
|
-
@master.instance_variable_get(
|
74
|
+
@master.instance_variable_get('@workers').each do |worker|
|
68
75
|
workers[worker] = GetProcessMem.new(worker.pid).mb
|
69
76
|
end
|
70
77
|
if workers.any?
|
71
|
-
@workers = Hash[
|
78
|
+
@workers = Hash[workers.sort_by { |_, mem| mem }]
|
72
79
|
else
|
73
80
|
{}
|
74
81
|
end
|
75
82
|
end
|
76
83
|
end
|
77
|
-
end
|
84
|
+
end
|
@@ -1,8 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PumaWorkerKiller
|
2
4
|
class Reaper
|
3
|
-
def initialize(max_ram, master = nil)
|
5
|
+
def initialize(max_ram, master = nil, reaper_status_logs = true, pre_term = nil, on_calculation = nil)
|
4
6
|
@cluster = PumaWorkerKiller::PumaMemory.new(master)
|
5
7
|
@max_ram = max_ram
|
8
|
+
@reaper_status_logs = reaper_status_logs
|
9
|
+
@pre_term = pre_term
|
10
|
+
@on_calculation = on_calculation
|
6
11
|
end
|
7
12
|
|
8
13
|
# used for tes
|
@@ -11,12 +16,27 @@ module PumaWorkerKiller
|
|
11
16
|
end
|
12
17
|
|
13
18
|
def reap
|
14
|
-
return false
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
return false if @cluster.workers_stopped?
|
20
|
+
|
21
|
+
total = get_total_memory
|
22
|
+
@on_calculation&.call(total)
|
23
|
+
|
24
|
+
if total > @max_ram
|
25
|
+
@cluster.master.log "PumaWorkerKiller: Out of memory. #{@cluster.workers.count} workers consuming total: #{total} mb out of max: #{@max_ram} mb. Sending TERM to pid #{@cluster.largest_worker.pid} consuming #{@cluster.largest_worker_memory} mb."
|
26
|
+
|
27
|
+
# Fetch the largest_worker so that both `@pre_term` and `term_worker` are called with the same worker
|
28
|
+
# Avoids a race condition where:
|
29
|
+
# Worker A consume 100 mb memory
|
30
|
+
# Worker B consume 99 mb memory
|
31
|
+
# pre_term gets called with Worker A
|
32
|
+
# A new request comes in, Worker B takes it, and consumes 101 mb memory
|
33
|
+
# term_largest_worker (previously here) gets called and terms Worker B (thus not passing the about-to-be-terminated worker to `@pre_term`)
|
34
|
+
largest_worker = @cluster.largest_worker
|
35
|
+
@pre_term&.call(largest_worker)
|
36
|
+
@cluster.term_worker(largest_worker)
|
37
|
+
|
38
|
+
elsif @reaper_status_logs
|
39
|
+
@cluster.master.log "PumaWorkerKiller: Consuming #{total} mb with master and #{@cluster.workers.count} workers."
|
20
40
|
end
|
21
41
|
end
|
22
42
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PumaWorkerKiller
|
2
4
|
class RollingRestart
|
3
|
-
def initialize(master = nil)
|
5
|
+
def initialize(master = nil, rolling_pre_term = nil)
|
4
6
|
@cluster = PumaWorkerKiller::PumaMemory.new(master)
|
7
|
+
@rolling_pre_term = rolling_pre_term
|
5
8
|
end
|
6
9
|
|
7
10
|
# used for tes
|
@@ -9,12 +12,17 @@ module PumaWorkerKiller
|
|
9
12
|
@cluster.get_total_memory
|
10
13
|
end
|
11
14
|
|
12
|
-
def reap(
|
15
|
+
def reap(seconds_between_worker_kill = 60)
|
16
|
+
# this will implicitly call set_workers
|
17
|
+
total_memory = get_total_memory
|
13
18
|
return false unless @cluster.running?
|
14
|
-
|
15
|
-
|
19
|
+
|
20
|
+
@cluster.workers.each do |worker, _ram|
|
21
|
+
@cluster.master.log "PumaWorkerKiller: Rolling Restart. #{@cluster.workers.count} workers consuming total: #{total_memory} mb. Sending TERM to pid #{worker.pid}."
|
22
|
+
@rolling_pre_term&.call(worker)
|
23
|
+
|
16
24
|
worker.term
|
17
|
-
sleep
|
25
|
+
sleep seconds_between_worker_kill
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
data/puma_worker_killer.gemspec
CHANGED
@@ -1,27 +1,28 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'puma_worker_killer/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name =
|
8
|
+
gem.name = 'puma_worker_killer'
|
8
9
|
gem.version = PumaWorkerKiller::VERSION
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
11
|
-
gem.description =
|
12
|
-
gem.summary =
|
13
|
-
gem.homepage =
|
14
|
-
gem.license =
|
10
|
+
gem.authors = ['Richard Schneeman']
|
11
|
+
gem.email = ['richard.schneeman+rubygems@gmail.com']
|
12
|
+
gem.description = ' Kills pumas, the code kind '
|
13
|
+
gem.summary = ' If you have a memory leak in your web code puma_worker_killer can keep it in check. '
|
14
|
+
gem.homepage = 'https://github.com/schneems/puma_worker_killer'
|
15
|
+
gem.license = 'MIT'
|
15
16
|
|
16
|
-
gem.files = `git ls-files`.split(
|
17
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
18
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
-
gem.require_paths = [
|
20
|
-
|
21
|
-
gem.add_dependency "puma", ">= 2.7", "< 4"
|
22
|
-
gem.add_dependency "get_process_mem", "~> 0.2"
|
23
|
-
gem.add_development_dependency "rack", "~> 1.6"
|
24
|
-
gem.add_development_dependency "rake", "~> 10.1"
|
25
|
-
gem.add_development_dependency "test-unit", ">= 0"
|
20
|
+
gem.require_paths = ['lib']
|
26
21
|
|
22
|
+
gem.add_dependency 'get_process_mem', '~> 0.2'
|
23
|
+
gem.add_dependency 'puma', '>= 2.7', '< 5'
|
24
|
+
gem.add_development_dependency 'rack', '~> 1.6'
|
25
|
+
gem.add_development_dependency 'rake', '~> 10.1'
|
26
|
+
gem.add_development_dependency 'test-unit', '>= 0'
|
27
|
+
gem.add_development_dependency 'wait_for_it', '~> 0.1'
|
27
28
|
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
1
5
|
require 'rack'
|
2
6
|
require 'rack/server'
|
3
7
|
|
@@ -7,21 +11,17 @@ PumaWorkerKiller.config do |config|
|
|
7
11
|
config.ram = Integer(ENV['PUMA_RAM']) if ENV['PUMA_RAM']
|
8
12
|
config.frequency = Integer(ENV['PUMA_FREQUENCY']) if ENV['PUMA_FREQUENCY']
|
9
13
|
end
|
10
|
-
PumaWorkerKiller.start
|
11
|
-
|
12
14
|
|
13
15
|
puts "Frequency: #{PumaWorkerKiller.frequency}" if ENV['PUMA_FREQUENCY']
|
14
16
|
|
15
17
|
class HelloWorld
|
16
|
-
def response
|
18
|
+
def response(_env)
|
17
19
|
[200, {}, ['Hello World']]
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
23
|
class HelloWorldApp
|
22
24
|
def self.call(env)
|
23
|
-
HelloWorld.new.response
|
25
|
+
HelloWorld.new.response(env)
|
24
26
|
end
|
25
27
|
end
|
26
|
-
|
27
|
-
run HelloWorldApp
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
load File.expand_path('fixture_helper.rb', __dir__)
|
4
|
+
|
5
|
+
PumaWorkerKiller.config do |config|
|
6
|
+
config.on_calculation = ->(usage) { puts("Current memory footprint: #{usage} mb") }
|
7
|
+
end
|
8
|
+
PumaWorkerKiller.start
|
9
|
+
|
10
|
+
run HelloWorldApp
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
load File.expand_path('fixture_helper.rb', __dir__)
|
4
|
+
|
5
|
+
PumaWorkerKiller.config do |config|
|
6
|
+
config.pre_term = ->(worker) { puts("About to terminate worker: #{worker.inspect}") }
|
7
|
+
end
|
8
|
+
PumaWorkerKiller.start
|
9
|
+
|
10
|
+
run HelloWorldApp
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
load File.expand_path('fixture_helper.rb', __dir__)
|
4
|
+
|
5
|
+
PumaWorkerKiller.config do |config|
|
6
|
+
config.rolling_pre_term = ->(worker) { puts("About to terminate (rolling) worker: #{worker.pid}") }
|
7
|
+
end
|
8
|
+
PumaWorkerKiller.enable_rolling_restart(1) # 1 second
|
9
|
+
|
10
|
+
run HelloWorldApp
|
@@ -1,77 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
class PumaWorkerKillerTest < Test::Unit::TestCase
|
4
|
-
|
5
6
|
def test_starts
|
6
|
-
app_path = fixture_path.join("app.ru")
|
7
7
|
port = 0 # http://stackoverflow.com/questions/200484/how-do-you-find-a-free-tcp-server-port-using-ruby
|
8
|
-
|
9
|
-
|
10
|
-
sleep 5
|
11
|
-
assert_match "PumaWorkerKiller:", puma_log.read
|
12
|
-
ensure
|
13
|
-
puma_log.delete
|
14
|
-
Process.kill('TERM', pid) if pid
|
15
|
-
end
|
8
|
+
command = "bundle exec puma #{fixture_path.join("default.ru")} -t 1:1 -w 2 --preload --debug -p #{port}"
|
9
|
+
options = { wait_for: 'booted', timeout: 5, env: { 'PUMA_FREQUENCY' => 1 } }
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
worker_count = 10
|
22
|
-
worker_count.times { cluster.add_worker }
|
11
|
+
WaitForIt.new(command, options) do |spawn|
|
12
|
+
assert_contains(spawn, 'PumaWorkerKiller')
|
13
|
+
end
|
14
|
+
end
|
23
15
|
|
24
|
-
|
25
|
-
|
16
|
+
def test_without_preload
|
17
|
+
port = 0 # http://stackoverflow.com/questions/200484/how-do-you-find-a-free-tcp-server-port-using-ruby
|
18
|
+
command = "bundle exec puma #{fixture_path.join("default.ru")} -t 1:1 -w 2 --debug -p #{port} -C #{fixture_path.join("config/puma_worker_killer_start.rb")}"
|
19
|
+
options = { wait_for: 'booted', timeout: 10, env: { 'PUMA_FREQUENCY' => 1 } }
|
26
20
|
|
27
|
-
|
28
|
-
|
21
|
+
WaitForIt.new(command, options) do |spawn|
|
22
|
+
assert_contains(spawn, 'PumaWorkerKiller')
|
23
|
+
end
|
24
|
+
end
|
29
25
|
|
30
|
-
|
31
|
-
|
26
|
+
def test_kills_large_app
|
27
|
+
file = fixture_path.join('big.ru')
|
28
|
+
port = 0
|
29
|
+
command = "bundle exec puma #{file} -t 1:1 -w 2 --preload --debug -p #{port}"
|
30
|
+
options = { wait_for: 'booted', timeout: 5, env: { 'PUMA_FREQUENCY' => 1, 'PUMA_RAM' => 1 } }
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
cluster.workers.map(&:term)
|
32
|
+
WaitForIt.new(command, options) do |spawn|
|
33
|
+
assert_contains(spawn, 'Out of memory')
|
34
|
+
end
|
37
35
|
end
|
38
36
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
def test_pre_term
|
38
|
+
file = fixture_path.join('pre_term.ru')
|
39
|
+
port = 0
|
40
|
+
command = "bundle exec puma #{file} -t 1:1 -w 2 --preload --debug -p #{port}"
|
41
|
+
options = { wait_for: 'booted', timeout: 5, env: { 'PUMA_FREQUENCY' => 1, 'PUMA_RAM' => 1 } }
|
42
|
+
|
43
|
+
WaitForIt.new(command, options) do |spawn|
|
44
|
+
assert_contains(spawn, 'Out of memory')
|
45
|
+
assert_contains(spawn, 'About to terminate worker:') # defined in pre_term.ru
|
46
46
|
end
|
47
|
+
end
|
47
48
|
|
48
|
-
|
49
|
-
|
49
|
+
def test_on_calculation
|
50
|
+
file = fixture_path.join('on_calculation.ru')
|
51
|
+
port = 0
|
52
|
+
command = "bundle exec puma #{file} -t 1:1 -w 2 --preload --debug -p #{port}"
|
53
|
+
options = { wait_for: 'booted', timeout: 5, env: { 'PUMA_FREQUENCY' => 1, 'PUMA_RAM' => 1 } }
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
WaitForIt.new(command, options) do |spawn|
|
56
|
+
assert_contains(spawn, 'Out of memory')
|
57
|
+
assert_contains(spawn, 'Current memory footprint:') # defined in on_calculate.ru
|
54
58
|
end
|
55
|
-
|
56
|
-
reaper.reap
|
57
|
-
assert_equal 1, cluster.workers.select {|w| w.is_term? }.count
|
58
|
-
ensure
|
59
|
-
cluster.workers.map(&:term)
|
60
59
|
end
|
61
60
|
|
61
|
+
def assert_contains(spawn, string)
|
62
|
+
assert spawn.wait(string), "Expected logs to contain '#{string}' but it did not, contents: #{spawn.log.read}"
|
63
|
+
end
|
62
64
|
|
63
65
|
def test_rolling_restart
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
file = fixture_path.join('rolling_restart.ru')
|
67
|
+
port = 0
|
68
|
+
command = "bundle exec puma #{file} -t 1:1 -w 2 --preload --debug -p #{port}"
|
69
|
+
puts command.inspect
|
70
|
+
options = { wait_for: 'booted', timeout: 15, env: {} }
|
71
|
+
|
72
|
+
WaitForIt.new(command, options) do |spawn|
|
73
|
+
assert_contains(spawn, 'Rolling Restart')
|
74
|
+
end
|
75
|
+
end
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
77
|
+
def test_rolling_restart_worker_kill_check
|
78
|
+
file = fixture_path.join('rolling_restart.ru')
|
79
|
+
port = 0
|
80
|
+
command = "bundle exec puma #{file} -t 1:1 -w 1 --preload --debug -p #{port}"
|
81
|
+
puts command.inspect
|
82
|
+
options = { wait_for: 'booted', timeout: 120, env: {} }
|
83
|
+
|
84
|
+
WaitForIt.new(command, options) do |spawn|
|
85
|
+
# at least 2 matches for TERM (so we set a timeout value longer - 120sec)
|
86
|
+
spawn.wait!(/TERM.*TERM/m)
|
87
|
+
term_ids = spawn.log.read.scan(/TERM to pid (\d*)/)
|
88
|
+
assert term_ids.sort == term_ids.uniq.sort
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_rolling_pre_term
|
93
|
+
file = fixture_path.join('rolling_pre_term.ru')
|
94
|
+
port = 0
|
95
|
+
command = "bundle exec puma #{file} -t 1:1 -w 2 --preload --debug -p #{port}"
|
96
|
+
puts command.inspect
|
97
|
+
options = { wait_for: 'booted', timeout: 15, env: {} }
|
71
98
|
|
72
|
-
|
73
|
-
|
74
|
-
|
99
|
+
WaitForIt.new(command, options) do |spawn|
|
100
|
+
assert_contains(spawn, 'Rolling Restart')
|
101
|
+
assert_contains(spawn, 'About to terminate (rolling) worker:') # defined in rolling_pre_term.ru
|
102
|
+
end
|
75
103
|
end
|
76
104
|
end
|
77
|
-
|
data/test/test_helper.rb
CHANGED
@@ -1,71 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Bundler.require
|
2
4
|
|
3
5
|
require 'puma_worker_killer'
|
4
6
|
require 'test/unit'
|
7
|
+
require 'wait_for_it'
|
5
8
|
|
6
9
|
def fixture_path
|
7
|
-
Pathname.new(File.expand_path(
|
8
|
-
end
|
9
|
-
|
10
|
-
# Mock object stand in for Puma::Cluster
|
11
|
-
class FakeCluster
|
12
|
-
def initialize
|
13
|
-
@workers = []
|
14
|
-
end
|
15
|
-
|
16
|
-
def wakeup!
|
17
|
-
end
|
18
|
-
|
19
|
-
class Worker
|
20
|
-
attr_accessor :pid
|
21
|
-
|
22
|
-
def initialize(pid)
|
23
|
-
@pid = pid
|
24
|
-
end
|
25
|
-
|
26
|
-
def memory_leak
|
27
|
-
while true
|
28
|
-
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# not public interface, used for testing
|
33
|
-
def is_term?
|
34
|
-
@first_term_sent
|
35
|
-
end
|
36
|
-
|
37
|
-
def term
|
38
|
-
begin
|
39
|
-
if @first_term_sent && (Time.new - @first_term_sent) > 30
|
40
|
-
@signal = "KILL"
|
41
|
-
else
|
42
|
-
@first_term_sent ||= Time.new
|
43
|
-
end
|
44
|
-
|
45
|
-
Process.kill "TERM", @pid
|
46
|
-
Process.wait(@pid)
|
47
|
-
rescue Errno::ESRCH, Errno::ECHILD
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def log(msg)
|
53
|
-
puts msg
|
54
|
-
end
|
55
|
-
|
56
|
-
def do_work
|
57
|
-
while true
|
58
|
-
sleep 1
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# not a public interface, added to make testing easier
|
63
|
-
def workers
|
64
|
-
@workers
|
65
|
-
end
|
66
|
-
|
67
|
-
def add_worker
|
68
|
-
pid = fork { do_work }
|
69
|
-
@workers << Worker.new(pid)
|
70
|
-
end
|
10
|
+
Pathname.new(File.expand_path('fixtures', __dir__))
|
71
11
|
end
|
metadata
CHANGED
@@ -1,49 +1,49 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma_worker_killer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Schneeman
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: get_process_mem
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.7'
|
20
|
-
- - "<"
|
17
|
+
- - "~>"
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
19
|
+
version: '0.2'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '2.7'
|
30
|
-
- - "<"
|
24
|
+
- - "~>"
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
26
|
+
version: '0.2'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
28
|
+
name: puma
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
|
-
- - "
|
31
|
+
- - ">="
|
38
32
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
33
|
+
version: '2.7'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '5'
|
40
37
|
type: :runtime
|
41
38
|
prerelease: false
|
42
39
|
version_requirements: !ruby/object:Gem::Requirement
|
43
40
|
requirements:
|
44
|
-
- - "
|
41
|
+
- - ">="
|
45
42
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
43
|
+
version: '2.7'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '5'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rack
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,6 +86,20 @@ dependencies:
|
|
86
86
|
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: wait_for_it
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0.1'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.1'
|
89
103
|
description: " Kills pumas, the code kind "
|
90
104
|
email:
|
91
105
|
- richard.schneeman+rubygems@gmail.com
|
@@ -93,7 +107,10 @@ executables: []
|
|
93
107
|
extensions: []
|
94
108
|
extra_rdoc_files: []
|
95
109
|
files:
|
110
|
+
- ".github/workflows/check_changelog.yml"
|
96
111
|
- ".gitignore"
|
112
|
+
- ".rubocop.yml"
|
113
|
+
- ".rubocop_todo.yml"
|
97
114
|
- ".travis.yml"
|
98
115
|
- CHANGELOG.md
|
99
116
|
- Gemfile
|
@@ -106,14 +123,21 @@ files:
|
|
106
123
|
- lib/puma_worker_killer/rolling_restart.rb
|
107
124
|
- lib/puma_worker_killer/version.rb
|
108
125
|
- puma_worker_killer.gemspec
|
109
|
-
- test/fixtures/
|
126
|
+
- test/fixtures/big.ru
|
127
|
+
- test/fixtures/config/puma_worker_killer_start.rb
|
128
|
+
- test/fixtures/default.ru
|
129
|
+
- test/fixtures/fixture_helper.rb
|
130
|
+
- test/fixtures/on_calculation.ru
|
131
|
+
- test/fixtures/pre_term.ru
|
132
|
+
- test/fixtures/rolling_pre_term.ru
|
133
|
+
- test/fixtures/rolling_restart.ru
|
110
134
|
- test/puma_worker_killer_test.rb
|
111
135
|
- test/test_helper.rb
|
112
136
|
homepage: https://github.com/schneems/puma_worker_killer
|
113
137
|
licenses:
|
114
138
|
- MIT
|
115
139
|
metadata: {}
|
116
|
-
post_install_message:
|
140
|
+
post_install_message:
|
117
141
|
rdoc_options: []
|
118
142
|
require_paths:
|
119
143
|
- lib
|
@@ -128,13 +152,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
152
|
- !ruby/object:Gem::Version
|
129
153
|
version: '0'
|
130
154
|
requirements: []
|
131
|
-
|
132
|
-
|
133
|
-
signing_key:
|
155
|
+
rubygems_version: 3.1.2
|
156
|
+
signing_key:
|
134
157
|
specification_version: 4
|
135
158
|
summary: If you have a memory leak in your web code puma_worker_killer can keep it
|
136
159
|
in check.
|
137
160
|
test_files:
|
138
|
-
- test/fixtures/
|
161
|
+
- test/fixtures/big.ru
|
162
|
+
- test/fixtures/config/puma_worker_killer_start.rb
|
163
|
+
- test/fixtures/default.ru
|
164
|
+
- test/fixtures/fixture_helper.rb
|
165
|
+
- test/fixtures/on_calculation.ru
|
166
|
+
- test/fixtures/pre_term.ru
|
167
|
+
- test/fixtures/rolling_pre_term.ru
|
168
|
+
- test/fixtures/rolling_restart.ru
|
139
169
|
- test/puma_worker_killer_test.rb
|
140
170
|
- test/test_helper.rb
|