gitlab-puma_worker_killer 0.1.1.gitlab.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c6b50540a891f76c28cbbf9d2515312e145b6d1fafb64ea5d47658875c50a87b
4
+ data.tar.gz: 2f4f93250b070b77fb03e094c711a5819263606ddf21857e8afa0f2160729ad0
5
+ SHA512:
6
+ metadata.gz: 6ca131cb1c2a24012a9d501f1773c5a0868904e2742b728f280688c4106792e76b573704e939869ccf08ac364560ef15d0538a8bd03105deffb96588ccdbd856
7
+ data.tar.gz: eeb62826df447cea6d2adda536b58661e19873382a83c4e844efaffe7969cc938b8caf9d9cd7cdf14107ecf72d1e62a84960840bbf650b7a161a7ed3a1e51c05
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ *.gem
3
+ puma.log
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.10
6
+ - 2.2.9
7
+ - 2.3.6
8
+ - 2.4.3
9
+ - 2.5.0
10
+ - ruby-head
11
+ - rbx
12
+ before_install:
13
+ - gem install bundler -v 1.12.5
14
+
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: ruby-head
18
+ - rvm: rbx
@@ -0,0 +1,27 @@
1
+ ## 0.1.1
2
+
3
+ - Allow PWK to be used with Puma 4 (#72)
4
+
5
+ ## 0.1.0
6
+
7
+ - Emit extra data via `pre_term` callback before puma worker killer terminates a worker #49.
8
+
9
+ ## 0.0.7
10
+
11
+ - Logging is configurable #41
12
+
13
+ ## 0.0.6
14
+
15
+ - Log PID of worker insead of inspecting the worker #33
16
+
17
+ ## 0.0.5
18
+
19
+ - Support for Puma 3.x
20
+
21
+ ## 0.0.4
22
+
23
+ - Add ability to do rolling restart
24
+
25
+ ## 0.0.3
26
+
27
+ - Fix memory metrics in on linux
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,196 @@
1
+ # Puma Worker Killer
2
+
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)
5
+
6
+
7
+ ## What
8
+
9
+ If you have a memory leak in your code, finding and plugging it can be a herculean effort. Instead what if you just killed your processes when they got to be too large? The Puma Worker Killer does just that. Similar to [Unicorn Worker Killer](https://github.com/kzk/unicorn-worker-killer) but for the Puma web server.
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+
16
+ ## Install
17
+
18
+ In your Gemfile add:
19
+
20
+ ```ruby
21
+ gem 'puma_worker_killer'
22
+ ```
23
+
24
+ Then run `$ bundle install`
25
+
26
+ <!--
27
+ ## Use
28
+
29
+ > 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!
30
+
31
+ -->
32
+
33
+ ## Turn on Rolling Restarts
34
+
35
+ 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:
36
+
37
+ ```ruby
38
+ # config/puma.rb
39
+
40
+ before_fork do
41
+ require 'puma_worker_killer'
42
+
43
+ PumaWorkerKiller.enable_rolling_restart # Default is every 6 hours
44
+ end
45
+
46
+ ```
47
+
48
+ or you can pass in the restart frequency:
49
+
50
+ ```ruby
51
+ PumaWorkerKiller.enable_rolling_restart(12 * 3600) # 12 hours in seconds
52
+ ```
53
+
54
+ Make sure if you do this to not accidentally call `PumaWorkerKiller.start` as well.
55
+
56
+ ## Enable Worker Killing
57
+
58
+ 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`:
59
+
60
+ ```ruby
61
+ # config/puma.rb
62
+
63
+ before_fork do
64
+ require 'puma_worker_killer'
65
+
66
+ PumaWorkerKiller.start
67
+ end
68
+ ```
69
+
70
+ 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.
71
+
72
+ ## Troubleshooting
73
+
74
+ When you boot your program locally you should see debug output:
75
+
76
+ ```
77
+ [77773] Puma starting in cluster mode...
78
+ [77773] * Version 3.1.0 (ruby 2.3.1-p112), codename: El Niño Winter Wonderland
79
+ [77773] * Min threads: 0, max threads: 16
80
+ [77773] * Environment: development
81
+ [77773] * Process workers: 2
82
+ [77773] * Phased restart available
83
+ [77773] * Listening on tcp://0.0.0.0:9292
84
+ [77773] Use Ctrl-C to stop
85
+ [77773] PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.
86
+ ```
87
+
88
+ 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:
89
+
90
+ ```
91
+ [77773] * Process workers: 2
92
+ ```
93
+
94
+ If you've configured PWK's frequency try reducing it to a very low value
95
+
96
+
97
+ ## Configure
98
+
99
+ Before calling `start` you can configure `PumaWorkerKiller`. You can do so using a configure block or calling methods directly:
100
+
101
+ ```ruby
102
+ PumaWorkerKiller.config do |config|
103
+ config.ram = 1024 # mb
104
+ config.frequency = 5 # seconds
105
+ config.percent_usage = 0.98
106
+ config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds, or 12.hours if using Rails
107
+ config.reaper_status_logs = true # setting this to false will not log lines like:
108
+ # PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.
109
+
110
+ config.pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed" }
111
+ end
112
+ PumaWorkerKiller.start
113
+ ```
114
+
115
+ ### pre_term
116
+
117
+ `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.
118
+
119
+ By default Puma Worker Killer will emit a log when a worker is being killed
120
+
121
+ ```
122
+ PumaWorkerKiller: Out of memory. 5 workers consuming total: 500 mb out of max: 450 mb. Sending TERM to pid 23 consuming 53 mb.
123
+ ```
124
+
125
+ or
126
+
127
+ ```
128
+ PumaWorkerKiller: Rolling Restart. 5 workers consuming total: 650mb mb. Sending TERM to pid 34.
129
+ ```
130
+
131
+ 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.
132
+
133
+ ### on_calculation
134
+
135
+ `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.
136
+
137
+ This callback lambda is given a single value for the amount of memory used.
138
+
139
+ ## Attention
140
+
141
+ If you start puma as a daemon, to add puma worker killer config into puma config file, rather than into initializers:
142
+ Sample like this: (in `config/puma.rb` file):
143
+
144
+ ```ruby
145
+ before_fork do
146
+ PumaWorkerKiller.config do |config|
147
+ config.ram = 1024 # mb
148
+ config.frequency = 5 # seconds
149
+ config.percent_usage = 0.98
150
+ config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds, or 12.hours if using Rails
151
+ end
152
+ PumaWorkerKiller.start
153
+ end
154
+ ```
155
+
156
+ It is important that you tell your code how much RAM is available on your system. The default is 512 mb (the same size as a Heroku 1x dyno). You can change this value like this:
157
+
158
+ ```ruby
159
+ PumaWorkerKiller.ram = 1024 # mb
160
+ ```
161
+
162
+ By default it is assumed that you do not want to hit 100% utilization, that is if your code is actually using 512 mb out of 512 mb it would be bad (this is dangerously close to swapping memory and slowing down your programs). So by default processes will be killed when they are at 99 % utilization of the value specified in `PumaWorkerKiller.ram`. You can change that value to 98 % like this:
163
+
164
+ ```ruby
165
+ PumaWorkerKiller.percent_usage = 0.98
166
+ ```
167
+
168
+ You may want to tune the worker killer to run more or less often. You can adjust frequency:
169
+
170
+ ```ruby
171
+ PumaWorkerKiller.frequency = 20 # seconds
172
+ ```
173
+
174
+ You may want to periodically restart all of your workers rather than simply killing your largest. To do that set:
175
+
176
+ ```ruby
177
+ PumaWorkerKiller.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds, or 12.hours if using Rails
178
+ ```
179
+
180
+ By default PumaWorkerKiller will perform a rolling restart of all your worker processes every 6 hours. To disable, set to `false`.
181
+
182
+ You may want to hide the following log lines: `PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.`. To do that set:
183
+
184
+ ```ruby
185
+ PumaWorkerKiller.reaper_status_logs = false
186
+ ```
187
+
188
+ Note: It is `true` by default.
189
+
190
+ ## License
191
+
192
+ MIT
193
+
194
+ ## Feedback
195
+
196
+ Open up an issue or ping me on twitter [@schneems](http://twitter.com/schneems).
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+
8
+ task :default => [:test]
9
+
10
+ test_task = Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.libs << 'test'
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = false
15
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'puma_worker_killer/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "gitlab-puma_worker_killer"
8
+ gem.version = PumaWorkerKiller::VERSION
9
+ gem.authors = ["Richard Schneeman", "GitLab"]
10
+ gem.email = ["richard.schneeman+rubygems@gmail.com", "kamil@gitlab.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"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency "gitlab-puma", ">= 2.7", "< 5"
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"
27
+
28
+ end
@@ -0,0 +1,38 @@
1
+ require 'get_process_mem'
2
+
3
+ module PumaWorkerKiller
4
+ extend self
5
+
6
+ attr_accessor :ram, :frequency, :percent_usage, :rolling_restart_frequency, :reaper_status_logs, :pre_term, :on_calculation
7
+ self.ram = 512 # mb
8
+ self.frequency = 10 # seconds
9
+ self.percent_usage = 0.99 # percent of RAM to use
10
+ self.rolling_restart_frequency = 6 * 3600 # 6 hours in seconds
11
+ self.reaper_status_logs = true
12
+ self.pre_term = nil
13
+ self.on_calculation = nil
14
+
15
+ def config
16
+ yield self
17
+ end
18
+
19
+ def reaper(ram = self.ram, percent = self.percent_usage, reaper_status_logs = self.reaper_status_logs, pre_term = self.pre_term, on_calculation = self.on_calculation)
20
+ Reaper.new(ram * percent_usage, nil, reaper_status_logs, pre_term, on_calculation)
21
+ end
22
+
23
+ def start(frequency = self.frequency, reaper = self.reaper)
24
+ AutoReap.new(frequency, reaper).start
25
+ enable_rolling_restart(rolling_restart_frequency) if rolling_restart_frequency
26
+ end
27
+
28
+ def enable_rolling_restart(frequency = self.rolling_restart_frequency)
29
+ frequency = frequency + rand(0..10.0) # so all workers don't restart at the exact same time across multiple machines
30
+ AutoReap.new(frequency, RollingRestart.new).start
31
+ end
32
+ end
33
+
34
+ require 'puma_worker_killer/puma_memory'
35
+ require 'puma_worker_killer/reaper'
36
+ require 'puma_worker_killer/rolling_restart'
37
+ require 'puma_worker_killer/auto_reap'
38
+ require 'puma_worker_killer/version'
@@ -0,0 +1,21 @@
1
+ module PumaWorkerKiller
2
+ class AutoReap
3
+ def initialize(timeout, reaper = Reaper.new)
4
+ @timeout = timeout # seconds
5
+ @reaper = reaper
6
+ @running = false
7
+ end
8
+
9
+ def start
10
+ @running = true
11
+
12
+ Thread.new do
13
+ while @running
14
+ sleep @timeout
15
+ @reaper.reap
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,85 @@
1
+ module PumaWorkerKiller
2
+ class PumaMemory
3
+ def initialize(master = nil)
4
+ @master = master || get_master
5
+ end
6
+
7
+ def master
8
+ @master
9
+ end
10
+
11
+ def size
12
+ workers.size
13
+ end
14
+
15
+ def term_worker(worker)
16
+ worker.term
17
+ end
18
+
19
+ def term_largest_worker
20
+ largest_worker.term
21
+ # Process.wait(largest_worker.pid)
22
+ # rescue Errno::ECHILD
23
+ end
24
+
25
+ def workers_stopped?
26
+ !running?
27
+ end
28
+
29
+ def running?
30
+ @master && workers.any?
31
+ end
32
+
33
+ def smallest_worker
34
+ smallest, _ = workers.to_a.first
35
+ smallest
36
+ end
37
+
38
+ def smallest_worker_memory
39
+ _, smallest_mem = workers.to_a.first
40
+ smallest_mem
41
+ end
42
+
43
+ def largest_worker
44
+ largest_worker, _ = workers.to_a.last
45
+ largest_worker
46
+ end
47
+
48
+ def largest_worker_memory
49
+ _, largest_memory_used = workers.to_a.last
50
+ largest_memory_used
51
+ end
52
+
53
+ # Will refresh @workers
54
+ def get_total(workers = set_workers)
55
+ master_memory = GetProcessMem.new(Process.pid).mb
56
+ worker_memory = workers.map {|_, mem| mem }.inject(&:+) || 0
57
+ worker_memory + master_memory
58
+ end
59
+ alias :get_total_memory :get_total
60
+
61
+ def workers
62
+ @workers || set_workers
63
+ end
64
+
65
+ private
66
+
67
+ def get_master
68
+ ObjectSpace.each_object(Puma::Cluster).map { |obj| obj }.first if defined?(Puma::Cluster)
69
+ end
70
+
71
+ # Returns sorted hash, keys are worker objects, values are memory used per worker
72
+ # sorted by memory ascending (smallest first, largest last)
73
+ def set_workers
74
+ workers = {}
75
+ @master.instance_variable_get("@workers").each do |worker|
76
+ workers[worker] = GetProcessMem.new(worker.pid).mb
77
+ end
78
+ if workers.any?
79
+ @workers = Hash[ workers.sort_by {|_, mem| mem } ]
80
+ else
81
+ {}
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,40 @@
1
+ module PumaWorkerKiller
2
+ class Reaper
3
+ def initialize(max_ram, master = nil, reaper_status_logs = true, pre_term = nil, on_calculation = nil)
4
+ @cluster = PumaWorkerKiller::PumaMemory.new(master)
5
+ @max_ram = max_ram
6
+ @reaper_status_logs = reaper_status_logs
7
+ @pre_term = pre_term
8
+ @on_calculation = on_calculation
9
+ end
10
+
11
+ # used for tes
12
+ def get_total_memory
13
+ @cluster.get_total_memory
14
+ end
15
+
16
+ def reap
17
+ return false if @cluster.workers_stopped?
18
+ total = get_total_memory
19
+ @on_calculation.call(total) unless @on_calculation.nil?
20
+
21
+ if total > @max_ram
22
+ @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."
23
+
24
+ # Fetch the largest_worker so that both `@pre_term` and `term_worker` are called with the same worker
25
+ # Avoids a race condition where:
26
+ # Worker A consume 100 mb memory
27
+ # Worker B consume 99 mb memory
28
+ # pre_term gets called with Worker A
29
+ # A new request comes in, Worker B takes it, and consumes 101 mb memory
30
+ # term_largest_worker (previously here) gets called and terms Worker B (thus not passing the about-to-be-terminated worker to `@pre_term`)
31
+ largest_worker = @cluster.largest_worker
32
+ @pre_term.call(largest_worker) unless @pre_term.nil?
33
+ @cluster.term_worker(largest_worker)
34
+
35
+ elsif @reaper_status_logs
36
+ @cluster.master.log "PumaWorkerKiller: Consuming #{total} mb with master and #{@cluster.workers.count} workers."
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module PumaWorkerKiller
2
+ class RollingRestart
3
+ def initialize(master = nil)
4
+ @cluster = PumaWorkerKiller::PumaMemory.new(master)
5
+ end
6
+
7
+ # used for tes
8
+ def get_total_memory
9
+ @cluster.get_total_memory
10
+ end
11
+
12
+ def reap(wait_between_worker_kill = 60) # seconds
13
+ 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. Sending TERM to pid #{worker.pid}."
16
+ worker.term
17
+ sleep wait_between_worker_kill
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module PumaWorkerKiller
2
+ VERSION = "0.1.1.gitlab.1"
3
+ end
@@ -0,0 +1,10 @@
1
+ load File.expand_path("../fixture_helper.rb", __FILE__)
2
+
3
+ PumaWorkerKiller.start
4
+
5
+ @memory = []
6
+ 10_000.times.each do
7
+ @memory << SecureRandom.hex
8
+ end
9
+
10
+ run HelloWorldApp
@@ -0,0 +1,6 @@
1
+ load File.expand_path("../../fixture_helper.rb", __FILE__)
2
+
3
+ before_fork do
4
+ require 'puma_worker_killer'
5
+ PumaWorkerKiller.start
6
+ end
@@ -0,0 +1,5 @@
1
+ load File.expand_path("../fixture_helper.rb", __FILE__)
2
+
3
+ PumaWorkerKiller.start
4
+
5
+ run HelloWorldApp
@@ -0,0 +1,25 @@
1
+ require 'securerandom'
2
+
3
+ require 'rack'
4
+ require 'rack/server'
5
+
6
+ require 'puma_worker_killer'
7
+
8
+ PumaWorkerKiller.config do |config|
9
+ config.ram = Integer(ENV['PUMA_RAM']) if ENV['PUMA_RAM']
10
+ config.frequency = Integer(ENV['PUMA_FREQUENCY']) if ENV['PUMA_FREQUENCY']
11
+ end
12
+
13
+ puts "Frequency: #{ PumaWorkerKiller.frequency }" if ENV['PUMA_FREQUENCY']
14
+
15
+ class HelloWorld
16
+ def response(env)
17
+ [200, {}, ['Hello World']]
18
+ end
19
+ end
20
+
21
+ class HelloWorldApp
22
+ def self.call(env)
23
+ HelloWorld.new.response(env)
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ load File.expand_path("../fixture_helper.rb", __FILE__)
2
+
3
+ PumaWorkerKiller.config do |config|
4
+ config.on_calculation = lambda { |usage| puts("Current memory footprint: #{usage} mb") }
5
+ end
6
+ PumaWorkerKiller.start
7
+
8
+ run HelloWorldApp
@@ -0,0 +1,8 @@
1
+ load File.expand_path("../fixture_helper.rb", __FILE__)
2
+
3
+ PumaWorkerKiller.config do |config|
4
+ config.pre_term = lambda { |worker| puts("About to terminate worker: #{worker.inspect}") }
5
+ end
6
+ PumaWorkerKiller.start
7
+
8
+ run HelloWorldApp
@@ -0,0 +1,5 @@
1
+ load File.expand_path("../fixture_helper.rb", __FILE__)
2
+
3
+ PumaWorkerKiller.enable_rolling_restart(1) # 1 second
4
+
5
+ run HelloWorldApp
@@ -0,0 +1,76 @@
1
+ require 'test_helper'
2
+
3
+ class PumaWorkerKillerTest < Test::Unit::TestCase
4
+
5
+ def test_starts
6
+ 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 } }
9
+
10
+ WaitForIt.new(command, options) do |spawn|
11
+ assert_contains(spawn, "PumaWorkerKiller")
12
+ end
13
+ end
14
+
15
+ def test_without_preload
16
+ 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 } }
19
+
20
+ WaitForIt.new(command, options) do |spawn|
21
+ assert_contains(spawn, "PumaWorkerKiller")
22
+ end
23
+ end
24
+
25
+ def test_kills_large_app
26
+ file = fixture_path.join("big.ru")
27
+ 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} }
30
+
31
+ WaitForIt.new(command, options) do |spawn|
32
+ assert_contains(spawn, "Out of memory")
33
+ end
34
+ end
35
+
36
+ def test_pre_term
37
+ file = fixture_path.join("pre_term.ru")
38
+ port = 0
39
+ command = "bundle exec puma #{ file } -t 1:1 -w 2 --preload --debug -p #{ port }"
40
+ options = { wait_for: "booted", timeout: 5, env: { "PUMA_FREQUENCY" => 1, 'PUMA_RAM' => 1} }
41
+
42
+ WaitForIt.new(command, options) do |spawn|
43
+ assert_contains(spawn, "Out of memory")
44
+ assert_contains(spawn, "About to terminate worker:") # defined in pre_term.ru
45
+ end
46
+ end
47
+
48
+ def test_on_calculation
49
+ file = fixture_path.join("on_calculation.ru")
50
+ port = 0
51
+ command = "bundle exec puma #{ file } -t 1:1 -w 2 --preload --debug -p #{ port }"
52
+ options = { wait_for: "booted", timeout: 5, env: { "PUMA_FREQUENCY" => 1, 'PUMA_RAM' => 1} }
53
+
54
+ WaitForIt.new(command, options) do |spawn|
55
+ assert_contains(spawn, "Out of memory")
56
+ assert_contains(spawn, "Current memory footprint:") # defined in on_calculate.ru
57
+ end
58
+ end
59
+
60
+ def assert_contains(spawn, string)
61
+ assert spawn.wait(string), "Expected logs to contain '#{string}' but it did not, contents: #{ spawn.log.read }"
62
+ end
63
+
64
+ def test_rolling_restart
65
+
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
76
+ end
@@ -0,0 +1,9 @@
1
+ Bundler.require
2
+
3
+ require 'puma_worker_killer'
4
+ require 'test/unit'
5
+ require 'wait_for_it'
6
+
7
+ def fixture_path
8
+ Pathname.new(File.expand_path("../fixtures", __FILE__))
9
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitlab-puma_worker_killer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1.gitlab.1
5
+ platform: ruby
6
+ authors:
7
+ - Richard Schneeman
8
+ - GitLab
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-12-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: gitlab-puma
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '2.7'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '5'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '2.7'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '5'
34
+ - !ruby/object:Gem::Dependency
35
+ name: get_process_mem
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rack
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ - !ruby/object:Gem::Dependency
63
+ name: wait_for_it
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.1'
76
+ - !ruby/object:Gem::Dependency
77
+ name: rake
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.1'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.1'
90
+ - !ruby/object:Gem::Dependency
91
+ name: test-unit
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ description: " Kills pumas, the code kind "
105
+ email:
106
+ - richard.schneeman+rubygems@gmail.com
107
+ - kamil@gitlab.com
108
+ executables: []
109
+ extensions: []
110
+ extra_rdoc_files: []
111
+ files:
112
+ - ".gitignore"
113
+ - ".travis.yml"
114
+ - CHANGELOG.md
115
+ - Gemfile
116
+ - README.md
117
+ - Rakefile
118
+ - gitlab-puma_worker_killer.gemspec
119
+ - lib/puma_worker_killer.rb
120
+ - lib/puma_worker_killer/auto_reap.rb
121
+ - lib/puma_worker_killer/puma_memory.rb
122
+ - lib/puma_worker_killer/reaper.rb
123
+ - lib/puma_worker_killer/rolling_restart.rb
124
+ - lib/puma_worker_killer/version.rb
125
+ - test/fixtures/big.ru
126
+ - test/fixtures/config/puma_worker_killer_start.rb
127
+ - test/fixtures/default.ru
128
+ - test/fixtures/fixture_helper.rb
129
+ - test/fixtures/on_calculation.ru
130
+ - test/fixtures/pre_term.ru
131
+ - test/fixtures/rolling_restart.ru
132
+ - test/puma_worker_killer_test.rb
133
+ - test/test_helper.rb
134
+ homepage: https://github.com/schneems/puma_worker_killer
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">"
150
+ - !ruby/object:Gem::Version
151
+ version: 1.3.1
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.7.6.2
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: If you have a memory leak in your web code puma_worker_killer can keep it
158
+ in check.
159
+ test_files:
160
+ - test/fixtures/big.ru
161
+ - test/fixtures/config/puma_worker_killer_start.rb
162
+ - test/fixtures/default.ru
163
+ - test/fixtures/fixture_helper.rb
164
+ - test/fixtures/on_calculation.ru
165
+ - test/fixtures/pre_term.ru
166
+ - test/fixtures/rolling_restart.ru
167
+ - test/puma_worker_killer_test.rb
168
+ - test/test_helper.rb