puma_worker_killer 0.0.7 → 0.3.1

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
- SHA1:
3
- metadata.gz: 20389b0e2ba67ab795071554a0e52b6599cd3612
4
- data.tar.gz: 0789e6a76e4c12394df935bd9b16b503d6990a62
2
+ SHA256:
3
+ metadata.gz: 0152a1c3c914cc4c71b9244e52704e89d6c3b92eb501f995b3237510a7baccad
4
+ data.tar.gz: a4c5a47dfd0bda390ffe32789e300bf04ebb98edcecd958a6e95742af8ae1bd1
5
5
  SHA512:
6
- metadata.gz: 62aa47f0f5032df0d0887e8001143059b4107fe1871932b545d4e56820c0012260ea73ffb77aed9be3fe83a1a3ec08b2f23633a05f96d8375885b3cdf1e05454
7
- data.tar.gz: 30fedd4919738c70432d7de4861089c03212c651805d4f564cd83e16d46f3b1d9bfc60ce44ae03a71cd1dff15b7547fbfb757b6b26d5e894f577ab0765669cad
6
+ metadata.gz: '049daa690327dcba057bd6e73cbf2834331017caac7d89b42fa92d391fe66e5207cbaed8662482d831f47a44abc78148866e1a9c257ebc19b013b9debb759ed0'
7
+ data.tar.gz: cada28b7b006572aece408d384c9dc744167d8a03b9d5567097eb1521c37e307fa38d9416896304afbcefb273b8ccf3057230bb275ef975fb8797d7b438f802d
@@ -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
@@ -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
@@ -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
@@ -1,10 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.0
6
- - 2.2.4
7
- - 2.3.0
3
+ - 2.3.8
4
+ - 2.4.10
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.1
8
8
  - ruby-head
9
9
  - rbx
10
10
  before_install:
@@ -14,3 +14,10 @@ matrix:
14
14
  allow_failures:
15
15
  - rvm: ruby-head
16
16
  - rvm: rbx
17
+
18
+ install:
19
+ - bundle install
20
+
21
+ script:
22
+ - bundle exec rubocop
23
+ - bundle exec rake test
@@ -1,3 +1,29 @@
1
+ ## Main
2
+
3
+ ## 0.3.1
4
+
5
+ - Relax puma dependency (#94)
6
+
7
+ ## 0.3.0
8
+
9
+ - Test on recent ruby versions #84
10
+ - Add option to adjust restart randomizer (#78)
11
+
12
+ ## 0.2.0
13
+
14
+ - Simplify workers memory calculation in PumaMemory‘s `get_total` method #81
15
+ - Add rubocop in gemspec and CI, with offenses corrected and unnecessary cops disabled.
16
+ - Add `pre_term`-like `rolling_pre_term` config for terminations caused by rolling restart (#86)
17
+ - Fix compatibility with ruby version 2.3.X (#87)
18
+
19
+ ## 0.1.1
20
+
21
+ - Allow PWK to be used with Puma 4 (#72)
22
+
23
+ ## 0.1.0
24
+
25
+ - Emit extra data via `pre_term` callback before puma worker killer terminates a worker #49.
26
+
1
27
  ## 0.0.7
2
28
 
3
29
  - Logging is configurable #41
@@ -17,3 +43,4 @@
17
43
  ## 0.0.3
18
44
 
19
45
  - Fix memory metrics in on linux
46
+
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  gemspec
6
+
7
+ gem 'rubocop', require: false
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
 
@@ -22,18 +39,13 @@ gem 'puma_worker_killer'
22
39
 
23
40
  Then run `$ bundle install`
24
41
 
25
- <!--
26
- ## Use
27
-
28
- > If you like `puma_worker_killer` consider using [puma_auto_tune instead](https://github.com/schneems/puma_auto_tune). It handles memory leaks and tunes your workers too!
29
-
30
- -->
31
-
32
- ## Turn on Rolling Restarts
42
+ ## Turn on Rolling Restarts - Heroku Mode
33
43
 
34
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:
35
45
 
36
46
  ```ruby
47
+ # config/puma.rb
48
+
37
49
  before_fork do
38
50
  require 'puma_worker_killer'
39
51
 
@@ -52,7 +64,7 @@ Make sure if you do this to not accidentally call `PumaWorkerKiller.start` as we
52
64
 
53
65
  ## Enable Worker Killing
54
66
 
55
- If you're not running on a containerized platform 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`:
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`:
56
68
 
57
69
  ```ruby
58
70
  # config/puma.rb
@@ -64,7 +76,7 @@ before_fork do
64
76
  end
65
77
  ```
66
78
 
67
- 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 a spawn a fresh copy with a much smaller RAM footprint ASAP.
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.
68
80
 
69
81
  ## Troubleshooting
70
82
 
@@ -100,13 +112,51 @@ PumaWorkerKiller.config do |config|
100
112
  config.ram = 1024 # mb
101
113
  config.frequency = 5 # seconds
102
114
  config.percent_usage = 0.98
103
- 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
104
116
  config.reaper_status_logs = true # setting this to false will not log lines like:
105
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" }
106
121
  end
107
122
  PumaWorkerKiller.start
108
123
  ```
109
124
 
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
+
110
160
  ## Attention
111
161
 
112
162
  If you start puma as a daemon, to add puma worker killer config into puma config file, rather than into initializers:
@@ -118,7 +168,7 @@ before_fork do
118
168
  config.ram = 1024 # mb
119
169
  config.frequency = 5 # seconds
120
170
  config.percent_usage = 0.98
121
- 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
122
172
  end
123
173
  PumaWorkerKiller.start
124
174
  end
@@ -145,10 +195,10 @@ PumaWorkerKiller.frequency = 20 # seconds
145
195
  You may want to periodically restart all of your workers rather than simply killing your largest. To do that set:
146
196
 
147
197
  ```ruby
148
- 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
149
199
  ```
150
200
 
151
- By default PumaWorkerKiller will perform a rolling restart of all your worker processes every 12 hours. To disable, set to `false`.
201
+ By default PumaWorkerKiller will perform a rolling restart of all your worker processes every 6 hours. To disable, set to `false`.
152
202
 
153
203
  You may want to hide the following log lines: `PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.`. To do that set:
154
204
 
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # encoding: UTF-8
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
- test_task = Rake::TestTask.new(:test) do |t|
10
+ Rake::TestTask.new(:test) do |t|
11
11
  t.libs << 'lib'
12
12
  t.libs << 'test'
13
13
  t.pattern = 'test/**/*_test.rb'
@@ -1,21 +1,30 @@
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, :reaper_status_logs
8
+ attr_accessor :ram, :frequency, :percent_usage, :rolling_restart_frequency,
9
+ :rolling_restart_splay_seconds,
10
+ :reaper_status_logs, :pre_term, :rolling_pre_term, :on_calculation
11
+
7
12
  self.ram = 512 # mb
8
13
  self.frequency = 10 # seconds
9
14
  self.percent_usage = 0.99 # percent of RAM to use
10
15
  self.rolling_restart_frequency = 6 * 3600 # 6 hours in seconds
16
+ self.rolling_restart_splay_seconds = 0.0..300.0 # 0 to 5 minutes in seconds
11
17
  self.reaper_status_logs = true
18
+ self.pre_term = nil
19
+ self.rolling_pre_term = nil
20
+ self.on_calculation = nil
12
21
 
13
22
  def config
14
23
  yield self
15
24
  end
16
25
 
17
- def reaper(ram = self.ram, percent = self.percent_usage, reaper_status_logs = self.reaper_status_logs)
18
- Reaper.new(ram * percent_usage, nil, reaper_status_logs)
26
+ 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)
27
+ Reaper.new(ram * percent_usage, nil, reaper_status_logs, pre_term, on_calculation)
19
28
  end
20
29
 
21
30
  def start(frequency = self.frequency, reaper = self.reaper)
@@ -23,9 +32,11 @@ module PumaWorkerKiller
23
32
  enable_rolling_restart(rolling_restart_frequency) if rolling_restart_frequency
24
33
  end
25
34
 
26
- def enable_rolling_restart(frequency = self.rolling_restart_frequency)
27
- frequency = frequency + rand(0..10.0) # so all workers don't restart at the exact same time across multiple machines
28
- AutoReap.new(frequency, RollingRestart.new).start
35
+ def enable_rolling_restart(frequency = rolling_restart_frequency,
36
+ splay_seconds = rolling_restart_splay_seconds)
37
+ # Randomize so all workers don't restart at the exact same time across multiple machines.
38
+ frequency += rand(splay_seconds)
39
+ AutoReap.new(frequency, RollingRestart.new(nil, rolling_pre_term)).start
29
40
  end
30
41
  end
31
42
 
@@ -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)
@@ -16,6 +18,5 @@ module PumaWorkerKiller
16
18
  end
17
19
  end
18
20
  end
19
-
20
21
  end
21
22
  end
@@ -1,21 +1,24 @@
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
- def master
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
- # Process.wait(largest_worker.pid)
18
- # rescue Errno::ECHILD
19
22
  end
20
23
 
21
24
  def workers_stopped?
@@ -27,7 +30,7 @@ module PumaWorkerKiller
27
30
  end
28
31
 
29
32
  def smallest_worker
30
- smallest, _ = workers.to_a.first
33
+ smallest, = workers.to_a.first
31
34
  smallest
32
35
  end
33
36
 
@@ -37,7 +40,7 @@ module PumaWorkerKiller
37
40
  end
38
41
 
39
42
  def largest_worker
40
- largest_worker, _ = workers.to_a.last
43
+ largest_worker, = workers.to_a.last
41
44
  largest_worker
42
45
  end
43
46
 
@@ -49,10 +52,10 @@ module PumaWorkerKiller
49
52
  # Will refresh @workers
50
53
  def get_total(workers = set_workers)
51
54
  master_memory = GetProcessMem.new(Process.pid).mb
52
- worker_memory = workers.map {|_, mem| mem }.inject(&:+) || 0
55
+ worker_memory = workers.values.inject(:+) || 0
53
56
  worker_memory + master_memory
54
57
  end
55
- alias :get_total_memory :get_total
58
+ alias get_total_memory get_total
56
59
 
57
60
  def workers
58
61
  @workers || set_workers
@@ -68,14 +71,14 @@ module PumaWorkerKiller
68
71
  # sorted by memory ascending (smallest first, largest last)
69
72
  def set_workers
70
73
  workers = {}
71
- @master.instance_variable_get("@workers").each do |worker|
74
+ @master.instance_variable_get('@workers').each do |worker|
72
75
  workers[worker] = GetProcessMem.new(worker.pid).mb
73
76
  end
74
77
  if workers.any?
75
- @workers = Hash[ workers.sort_by {|_, mem| mem } ]
78
+ @workers = Hash[workers.sort_by { |_, mem| mem }]
76
79
  else
77
80
  {}
78
81
  end
79
82
  end
80
83
  end
81
- end
84
+ end
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PumaWorkerKiller
2
4
  class Reaper
3
- def initialize(max_ram, master = nil, reaper_status_logs = true)
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
6
8
  @reaper_status_logs = reaper_status_logs
9
+ @pre_term = pre_term
10
+ @on_calculation = on_calculation
7
11
  end
8
12
 
9
13
  # used for tes
@@ -13,9 +17,24 @@ module PumaWorkerKiller
13
17
 
14
18
  def reap
15
19
  return false if @cluster.workers_stopped?
16
- if (total = get_total_memory) > @max_ram
20
+
21
+ total = get_total_memory
22
+ @on_calculation&.call(total)
23
+
24
+ if total > @max_ram
17
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."
18
- @cluster.term_largest_worker
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
+
19
38
  elsif @reaper_status_logs
20
39
  @cluster.master.log "PumaWorkerKiller: Consuming #{total} mb with master and #{@cluster.workers.count} workers."
21
40
  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(wait_between_worker_kill = 60) # seconds
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
- @cluster.workers.each do |worker, ram|
15
- @cluster.master.log "PumaWorkerKiller: Rolling Restart. #{@cluster.workers.count} workers consuming total: #{ get_total_memory } mb out of max: #{@max_ram} mb. Sending TERM to pid #{worker.pid}."
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 wait_between_worker_kill
25
+ sleep seconds_between_worker_kill
18
26
  end
19
27
  end
20
28
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PumaWorkerKiller
2
- VERSION = "0.0.7"
4
+ VERSION = '0.3.1'
3
5
  end
@@ -1,28 +1,28 @@
1
- # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
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 = "puma_worker_killer"
8
+ gem.name = 'puma_worker_killer'
8
9
  gem.version = PumaWorkerKiller::VERSION
9
- gem.authors = ["Richard Schneeman"]
10
- gem.email = ["richard.schneeman+rubygems@gmail.com"]
11
- gem.description = %q{ Kills pumas, the code kind }
12
- gem.summary = %q{ If you have a memory leak in your web code puma_worker_killer can keep it in check. }
13
- gem.homepage = "https://github.com/schneems/puma_worker_killer"
14
- gem.license = "MIT"
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 = ["lib"]
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 "wait_for_it", "~> 0.1"
25
- gem.add_development_dependency "rake", "~> 10.1"
26
- gem.add_development_dependency "test-unit", ">= 0"
20
+ gem.require_paths = ['lib']
27
21
 
22
+ gem.add_dependency 'get_process_mem', '~> 0.2'
23
+ gem.add_dependency 'puma', '>= 2.7'
24
+ gem.add_development_dependency 'rack', '~> 2.0'
25
+ gem.add_development_dependency 'rake', '~> 13.0'
26
+ gem.add_development_dependency 'test-unit', '>= 0'
27
+ gem.add_development_dependency 'wait_for_it', '~> 0.1'
28
28
  end
@@ -1,4 +1,6 @@
1
- load File.expand_path("../fixture_helper.rb", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ load File.expand_path('fixture_helper.rb', __dir__)
2
4
 
3
5
  PumaWorkerKiller.start
4
6
 
@@ -1,4 +1,6 @@
1
- load File.expand_path("../../fixture_helper.rb", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ load File.expand_path('../fixture_helper.rb', __dir__)
2
4
 
3
5
  before_fork do
4
6
  require 'puma_worker_killer'
@@ -1,4 +1,6 @@
1
- load File.expand_path("../fixture_helper.rb", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ load File.expand_path('fixture_helper.rb', __dir__)
2
4
 
3
5
  PumaWorkerKiller.start
4
6
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
5
  require 'rack'
@@ -10,10 +12,10 @@ PumaWorkerKiller.config do |config|
10
12
  config.frequency = Integer(ENV['PUMA_FREQUENCY']) if ENV['PUMA_FREQUENCY']
11
13
  end
12
14
 
13
- puts "Frequency: #{ PumaWorkerKiller.frequency }" if ENV['PUMA_FREQUENCY']
15
+ puts "Frequency: #{PumaWorkerKiller.frequency}" if ENV['PUMA_FREQUENCY']
14
16
 
15
17
  class HelloWorld
16
- def response(env)
18
+ def response(_env)
17
19
  [200, {}, ['Hello World']]
18
20
  end
19
21
  end
@@ -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, 0..5.0) # 1 second, short 1-5s splay.
9
+
10
+ run HelloWorldApp
@@ -1,5 +1,7 @@
1
- load File.expand_path("../fixture_helper.rb", __FILE__)
1
+ # frozen_string_literal: true
2
2
 
3
- PumaWorkerKiller.enable_rolling_restart(1) # 1 second
3
+ load File.expand_path('fixture_helper.rb', __dir__)
4
+
5
+ PumaWorkerKiller.enable_rolling_restart(1, 0..5.0) # 1 second, short 1-5s splay.
4
6
 
5
7
  run HelloWorldApp
@@ -1,53 +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
7
  port = 0 # http://stackoverflow.com/questions/200484/how-do-you-find-a-free-tcp-server-port-using-ruby
7
- command = "bundle exec puma #{ fixture_path.join("default.ru") } -t 1:1 -w 2 --preload --debug -p #{ port }"
8
- options = { wait_for: "booted", timeout: 5, env: { "PUMA_FREQUENCY" => 1 } }
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 } }
9
10
 
10
11
  WaitForIt.new(command, options) do |spawn|
11
- assert_contains(spawn, "PumaWorkerKiller")
12
+ assert_contains(spawn, 'PumaWorkerKiller')
12
13
  end
13
14
  end
14
15
 
15
16
  def test_without_preload
16
17
  port = 0 # http://stackoverflow.com/questions/200484/how-do-you-find-a-free-tcp-server-port-using-ruby
17
- 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") }"
18
- options = { wait_for: "booted", timeout: 10, env: { "PUMA_FREQUENCY" => 1 } }
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 } }
19
20
 
20
21
  WaitForIt.new(command, options) do |spawn|
21
- assert_contains(spawn, "PumaWorkerKiller")
22
+ assert_contains(spawn, 'PumaWorkerKiller')
22
23
  end
23
24
  end
24
25
 
25
26
  def test_kills_large_app
26
- file = fixture_path.join("big.ru")
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 } }
31
+
32
+ WaitForIt.new(command, options) do |spawn|
33
+ assert_contains(spawn, 'Out of memory')
34
+ end
35
+ end
36
+
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
+ end
47
+ end
48
+
49
+ def test_on_calculation
50
+ file = fixture_path.join('on_calculation.ru')
27
51
  port = 0
28
- command = "bundle exec puma #{ file } -t 1:1 -w 2 --preload --debug -p #{ port }"
29
- options = { wait_for: "booted", timeout: 5, env: { "PUMA_FREQUENCY" => 1, 'PUMA_RAM' => 1} }
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 } }
30
54
 
31
55
  WaitForIt.new(command, options) do |spawn|
32
- assert_contains(spawn, "Out of memory")
56
+ assert_contains(spawn, 'Out of memory')
57
+ assert_contains(spawn, 'Current memory footprint:') # defined in on_calculate.ru
33
58
  end
34
59
  end
35
60
 
36
61
  def assert_contains(spawn, string)
37
- assert spawn.wait(string), "Expected logs to contain '#{string}' but it did not, contents: #{ spawn.log.read }"
62
+ assert spawn.wait(string), "Expected logs to contain '#{string}' but it did not, contents: #{spawn.log.read}"
38
63
  end
39
64
 
40
65
  def test_rolling_restart
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: {} }
41
71
 
42
- file = fixture_path.join("rolling_restart.ru")
72
+ WaitForIt.new(command, options) do |spawn|
73
+ assert_contains(spawn, 'Rolling Restart')
74
+ end
75
+ end
76
+
77
+ def test_rolling_restart_worker_kill_check
78
+ file = fixture_path.join('rolling_restart.ru')
43
79
  port = 0
44
- command = "bundle exec puma #{ file } -t 1:1 -w 2 --preload --debug -p #{ port }"
80
+ command = "bundle exec puma #{file} -t 1:1 -w 1 --preload --debug -p #{port}"
45
81
  puts command.inspect
46
- options = { wait_for: "booted", timeout: 15, env: { } }
82
+ options = { wait_for: 'booted', timeout: 120, env: {} }
47
83
 
48
84
  WaitForIt.new(command, options) do |spawn|
49
- assert_contains(spawn, "Rolling Restart")
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
50
89
  end
51
90
  end
52
- end
53
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: {} }
98
+
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
103
+ end
104
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Bundler.require
2
4
 
3
5
  require 'puma_worker_killer'
@@ -5,5 +7,5 @@ require 'test/unit'
5
7
  require 'wait_for_it'
6
8
 
7
9
  def fixture_path
8
- Pathname.new(File.expand_path("../fixtures", __FILE__))
10
+ Pathname.new(File.expand_path('fixtures', __dir__))
9
11
  end
metadata CHANGED
@@ -1,35 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma_worker_killer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.3.1
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: 2016-10-13 00:00:00.000000000 Z
11
+ date: 2020-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: puma
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '2.7'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '4'
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: '2.7'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '4'
33
13
  - !ruby/object:Gem::Dependency
34
14
  name: get_process_mem
35
15
  requirement: !ruby/object:Gem::Requirement
@@ -45,47 +25,47 @@ dependencies:
45
25
  - !ruby/object:Gem::Version
46
26
  version: '0.2'
47
27
  - !ruby/object:Gem::Dependency
48
- name: rack
28
+ name: puma
49
29
  requirement: !ruby/object:Gem::Requirement
50
30
  requirements:
51
- - - "~>"
31
+ - - ">="
52
32
  - !ruby/object:Gem::Version
53
- version: '1.6'
54
- type: :development
33
+ version: '2.7'
34
+ type: :runtime
55
35
  prerelease: false
56
36
  version_requirements: !ruby/object:Gem::Requirement
57
37
  requirements:
58
- - - "~>"
38
+ - - ">="
59
39
  - !ruby/object:Gem::Version
60
- version: '1.6'
40
+ version: '2.7'
61
41
  - !ruby/object:Gem::Dependency
62
- name: wait_for_it
42
+ name: rack
63
43
  requirement: !ruby/object:Gem::Requirement
64
44
  requirements:
65
45
  - - "~>"
66
46
  - !ruby/object:Gem::Version
67
- version: '0.1'
47
+ version: '2.0'
68
48
  type: :development
69
49
  prerelease: false
70
50
  version_requirements: !ruby/object:Gem::Requirement
71
51
  requirements:
72
52
  - - "~>"
73
53
  - !ruby/object:Gem::Version
74
- version: '0.1'
54
+ version: '2.0'
75
55
  - !ruby/object:Gem::Dependency
76
56
  name: rake
77
57
  requirement: !ruby/object:Gem::Requirement
78
58
  requirements:
79
59
  - - "~>"
80
60
  - !ruby/object:Gem::Version
81
- version: '10.1'
61
+ version: '13.0'
82
62
  type: :development
83
63
  prerelease: false
84
64
  version_requirements: !ruby/object:Gem::Requirement
85
65
  requirements:
86
66
  - - "~>"
87
67
  - !ruby/object:Gem::Version
88
- version: '10.1'
68
+ version: '13.0'
89
69
  - !ruby/object:Gem::Dependency
90
70
  name: test-unit
91
71
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +80,20 @@ dependencies:
100
80
  - - ">="
101
81
  - !ruby/object:Gem::Version
102
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: wait_for_it
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.1'
103
97
  description: " Kills pumas, the code kind "
104
98
  email:
105
99
  - richard.schneeman+rubygems@gmail.com
@@ -107,7 +101,10 @@ executables: []
107
101
  extensions: []
108
102
  extra_rdoc_files: []
109
103
  files:
104
+ - ".github/workflows/check_changelog.yml"
110
105
  - ".gitignore"
106
+ - ".rubocop.yml"
107
+ - ".rubocop_todo.yml"
111
108
  - ".travis.yml"
112
109
  - CHANGELOG.md
113
110
  - Gemfile
@@ -124,6 +121,9 @@ files:
124
121
  - test/fixtures/config/puma_worker_killer_start.rb
125
122
  - test/fixtures/default.ru
126
123
  - test/fixtures/fixture_helper.rb
124
+ - test/fixtures/on_calculation.ru
125
+ - test/fixtures/pre_term.ru
126
+ - test/fixtures/rolling_pre_term.ru
127
127
  - test/fixtures/rolling_restart.ru
128
128
  - test/puma_worker_killer_test.rb
129
129
  - test/test_helper.rb
@@ -131,7 +131,7 @@ homepage: https://github.com/schneems/puma_worker_killer
131
131
  licenses:
132
132
  - MIT
133
133
  metadata: {}
134
- post_install_message:
134
+ post_install_message:
135
135
  rdoc_options: []
136
136
  require_paths:
137
137
  - lib
@@ -146,9 +146,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
146
  - !ruby/object:Gem::Version
147
147
  version: '0'
148
148
  requirements: []
149
- rubyforge_project:
150
- rubygems_version: 2.6.4
151
- signing_key:
149
+ rubygems_version: 3.1.2
150
+ signing_key:
152
151
  specification_version: 4
153
152
  summary: If you have a memory leak in your web code puma_worker_killer can keep it
154
153
  in check.
@@ -157,7 +156,9 @@ test_files:
157
156
  - test/fixtures/config/puma_worker_killer_start.rb
158
157
  - test/fixtures/default.ru
159
158
  - test/fixtures/fixture_helper.rb
159
+ - test/fixtures/on_calculation.ru
160
+ - test/fixtures/pre_term.ru
161
+ - test/fixtures/rolling_pre_term.ru
160
162
  - test/fixtures/rolling_restart.ru
161
163
  - test/puma_worker_killer_test.rb
162
164
  - test/test_helper.rb
163
- has_rdoc: