stalin 0.0.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +54 -11
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/stalin/adapter.rb +2 -0
- data/lib/stalin/adapter/puma.rb +32 -0
- data/lib/stalin/adapter/rack.rb +30 -21
- data/lib/stalin/adapter/unicorn.rb +15 -0
- data/lib/stalin/killer.rb +15 -13
- data/stalin.gemspec +13 -4
- metadata +21 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f573c1d3fa4d4b5247495ab63ef77a0c1f90471e5ab6efb232285c8321b33c82
|
4
|
+
data.tar.gz: 928cfad72a40b1f72b810520c42cc0e30a4f50d95da1cef3978892efada8dad5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8120043d4aeab951066cb0ecc172d437e58072313feacbe2ba6442c7b69a25bb205c4d6c2f7802933ea67eac097c3c1cafa79bcc961adb0fde1d517e25aee0c8
|
7
|
+
data.tar.gz: ea0707661dbb0fcdb7efa233fd3950a219e59c53667bbaccaeb10e611b8fba8a07d597b7aaa76fa7d5bc8cb36ab444eee92cb38d705d13b92e85ebd5d9359e63
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Given Ruby's proclivity for heap fragmentation, Web application worker processes
|
|
4
4
|
all available memory unless the server is restarted periodically. When all of the workers restart
|
5
5
|
at once, downtime or bad request throughput may result.
|
6
6
|
|
7
|
-
Stalin is a gem that
|
7
|
+
Stalin is a gem that gracefully kills your workers before they cause swapping, resulting in better
|
8
8
|
availability for your application. Its design goals are modularity and compatibility with a range
|
9
9
|
of platforms and app servers.
|
10
10
|
|
@@ -18,30 +18,73 @@ Data sources include:
|
|
18
18
|
Supported app servers include:
|
19
19
|
- Rainbows (via Rack middleware + SIGQUIT)
|
20
20
|
- Unicorn (via Rack middleware + SIGQUIT)
|
21
|
+
- Puma (via Rack middleware + SIGTERM/SIGUSR2 depending on execution model)
|
21
22
|
|
22
|
-
|
23
|
-
|
23
|
+
Servers known NOT to work:
|
24
|
+
- Thin (no supervisor process; need an adapter that execs or something)
|
24
25
|
|
25
|
-
|
26
|
+
As you can see, we are far short of our _goal_ to support many servers! More to come as needed;
|
27
|
+
let me know what you need!
|
28
|
+
|
29
|
+
# User's Guide
|
30
|
+
|
31
|
+
## Installation
|
26
32
|
|
27
33
|
Just include stalin in your Gemfile.
|
28
34
|
|
29
35
|
gem 'stalin'
|
30
36
|
|
31
|
-
|
37
|
+
## Usage
|
32
38
|
|
33
|
-
|
39
|
+
Decide on which application server you will use. Add some lines to your `config.ru` to
|
40
|
+
install a suitable Stalin middleware.
|
34
41
|
|
35
|
-
|
42
|
+
Because different app servers have different signal-handling and restart semantics, we
|
43
|
+
must specialize Stalin's behavior; this is done with an abstract base class
|
44
|
+
(Stalin::Adapter::Rack) plus one derived class per supported application server.
|
45
|
+
|
46
|
+
# Gem that kills app processes when their heap becomes too fragmented.
|
36
47
|
require 'stalin'
|
37
48
|
|
38
|
-
# Max memory size (RSS) per worker
|
39
49
|
mb = 1024**2
|
40
|
-
|
50
|
+
|
51
|
+
# Use the Unicorn adapter to ensure we send Unicorn-friendly kill signals.
|
52
|
+
# Each worker will shutdown at some point between 192MB and 256MB of memory usage.
|
53
|
+
use Stalin::Adapter::Unicorn, (192*mb), (256*mb)
|
54
|
+
|
55
|
+
## Tuning
|
56
|
+
|
57
|
+
Consult the documentation for your adapter's `#initialize` to learn how to tune Stalin's behavior.
|
58
|
+
|
59
|
+
# Developer's Guide
|
60
|
+
|
61
|
+
This gem is a work in progress; the docs are okay, but test coverage is
|
62
|
+
nonexistent. Whenever you make changes, please do some smoke tests by yourself.
|
63
|
+
|
64
|
+
## Smoke Tests
|
65
|
+
|
66
|
+
The `fixtures` subdirectory contains rackup files for both supported application
|
67
|
+
servers; you can use these to test the three supported permutations.
|
68
|
+
|
69
|
+
When you run puma or rainbows, it will begin listening on a port of its
|
70
|
+
choosing; use curl or similar to send it Web requests and cause a large
|
71
|
+
memory leak with every request. Each worker process should shutdown after the
|
72
|
+
first request, because the leak is large and the hardcoded limit for puma is
|
73
|
+
very small.
|
74
|
+
|
75
|
+
Verify that stalin is restarting the app servers and that your requests all
|
76
|
+
respond with 200 and not 502, 503 or other funny errors.
|
77
|
+
|
78
|
+
### Unicorn
|
79
|
+
|
80
|
+
bundle exec unicorn fixtures/unicorn.ru
|
81
|
+
### Single-Process Puma
|
41
82
|
|
42
|
-
|
83
|
+
bundle exec puma fixtures/puma.ru
|
84
|
+
|
85
|
+
### Multi-Process Puma
|
43
86
|
|
44
|
-
|
87
|
+
bundle exec puma -w 1 fixtures/puma.ru
|
45
88
|
|
46
89
|
# Special Thanks
|
47
90
|
|
data/Rakefile
CHANGED
@@ -9,6 +9,7 @@ Jeweler::Tasks.new do |gem|
|
|
9
9
|
gem.description = %Q{Kill Web application workers based on arbitrary conditions.}
|
10
10
|
gem.email = "xeger@xeger.net"
|
11
11
|
gem.authors = ["Tony Spataro"]
|
12
|
+
gem.required_ruby_version = '~> 2.0'
|
12
13
|
gem.files.exclude ".rspec"
|
13
14
|
gem.files.exclude "Gemfile*"
|
14
15
|
gem.files.exclude "fixtures/**/*"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.1
|
data/lib/stalin/adapter.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Stalin::Adapter
|
2
|
+
# A Stalin adapter that is suitable for use in Puma worker processes. Puma has two
|
3
|
+
# execution models.
|
4
|
+
# - "Cluster" spawns N worker processes; each responds to SIGTERM with graceful shutdown and SIGQUIT with abrupt shutdown
|
5
|
+
# - "Single" runs a server in the one-and-only master process; it responds to SIGUSR2 with graceflu restart
|
6
|
+
class Puma < Rack
|
7
|
+
# Create a middleware instance.
|
8
|
+
#
|
9
|
+
# @param [#call] app inner Rack application
|
10
|
+
# @param [Integer] min lower-bound worker memory consumption before restart
|
11
|
+
# @param [Integer] max upper-bound worker memory consumption before restart
|
12
|
+
# @param [Integer] cycle how frequently to check memory consumption (# requests)
|
13
|
+
# @param [Boolean] verbose log extra information
|
14
|
+
def initialize(app, min=1024**3, max=2*1024**3, cycle=16, verbose=false)
|
15
|
+
# Puma has no singletons, so we need to grab a reference to its CLI object.
|
16
|
+
# The CLI will not exist if puma was invoked through rackup.
|
17
|
+
#
|
18
|
+
# Since the master forks its workers, the CLI object also exists in workers.
|
19
|
+
# By the time this code runs, we'll always be in a worker.
|
20
|
+
cli = nil
|
21
|
+
ObjectSpace.each_object(::Puma::CLI) { |o| cli = o } if defined?(::Puma::CLI)
|
22
|
+
|
23
|
+
if cli && cli.clustered? && cli.options[:workers] > 0
|
24
|
+
# Multiprocess model: send ourselves SIGTERM
|
25
|
+
super(app, :TERM, :QUIT, min, max, cycle, verbose)
|
26
|
+
else
|
27
|
+
# Single-process model ("rackup," or "puma -w 0")
|
28
|
+
super(app, :USR2, :USR1, min, max, cycle, verbose)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/stalin/adapter/rack.rb
CHANGED
@@ -1,24 +1,29 @@
|
|
1
1
|
module Stalin::Adapter
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# worker processes as needed.
|
2
|
+
# Abstract base class for server-specific Rack middlewares.
|
3
|
+
# there is an app server that uses a signalling strategy not explicitly supported by Stalin.
|
5
4
|
class Rack
|
6
5
|
# Conversion constant for human-readable memory amounts in log messages.
|
7
6
|
MB = Float(1024**2)
|
8
7
|
|
9
8
|
# Create a middleware instance.
|
10
9
|
#
|
11
|
-
# @param [#call] app
|
10
|
+
# @param [#call] app inner Rack application
|
11
|
+
# @param [Symbol] graceful name of graceful-shutdown signal
|
12
|
+
# @param [Symbol] abrupt name of abrupt-shutdown signal
|
12
13
|
# @param [Integer] min lower-bound worker memory consumption before restart
|
13
14
|
# @param [Integer] max upper-bound worker memory consumption before restart
|
14
15
|
# @param [Integer] cycle how frequently to check memory consumption (# requests)
|
15
16
|
# @param [Boolean] verbose log extra information
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
17
|
+
# @param [Array] signals pair of two Symbol signal-names: one for "graceful shutdown please" and one for "terminate immediately"
|
18
|
+
def initialize(app, graceful, abrupt, min, max, cycle, verbose)
|
19
|
+
@app = app
|
20
|
+
@graceful = graceful
|
21
|
+
@abrupt = abrupt
|
22
|
+
@min = min
|
23
|
+
@max = max
|
24
|
+
@cycle = cycle
|
25
|
+
@verbose = verbose
|
26
|
+
@req = 0
|
22
27
|
end
|
23
28
|
|
24
29
|
def call(env)
|
@@ -27,22 +32,26 @@ module Stalin::Adapter
|
|
27
32
|
logger = logger_for(env)
|
28
33
|
|
29
34
|
begin
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
if @req == 0
|
36
|
+
# First-time initialization. Deferred until first request so we can
|
37
|
+
# ensure that init-time log output goes to the right place.
|
38
|
+
@lim = @min + randomize(@max - @min + 1)
|
39
|
+
@req = 0
|
40
|
+
@watcher = ::Stalin::Watcher.new(Process.pid)
|
41
|
+
@killer = ::Stalin::Killer.new(Process.pid, @graceful, @abrupt)
|
42
|
+
logger.info "stalin (pid: %d) startup; limit=%.1f MB, graceful=SIG%s (abrupt=SIG%s after %d tries)" %
|
43
|
+
[Process.pid, @lim / MB, @graceful, @abrupt, Stalin::Killer::MAX_GRACEFUL]
|
44
|
+
end
|
45
|
+
|
46
|
+
@req += 1
|
33
47
|
|
34
48
|
if @req % @cycle == 0
|
35
|
-
@req = 0
|
36
|
-
@watcher ||= ::Stalin::Watcher.new(Process.pid)
|
37
|
-
@killer ||= ::Stalin::Killer.new(Process.pid)
|
38
49
|
if (used = @watcher.watch) > @lim
|
39
50
|
sig = @killer.kill
|
40
|
-
|
41
|
-
logger.info "stalin (pid: %d) send SIG%s; memory usage %.1f MB > %.1f MB" %
|
51
|
+
logger.info "stalin (pid: %d) send SIG%s; %.1f MB > %.1f MB" %
|
42
52
|
[Process.pid, sig, used / MB, @lim / MB]
|
43
|
-
@cycle = 2
|
44
53
|
elsif @verbose
|
45
|
-
logger.info "stalin (pid: %d) soldiers on;
|
54
|
+
logger.info "stalin (pid: %d) soldiers on; %.1f MB < %.1f MB" %
|
46
55
|
[Process.pid, used / MB, @lim / MB]
|
47
56
|
end
|
48
57
|
end
|
@@ -57,7 +66,7 @@ module Stalin::Adapter
|
|
57
66
|
private
|
58
67
|
|
59
68
|
def randomize(integer)
|
60
|
-
|
69
|
+
Random.rand(integer.abs)
|
61
70
|
end
|
62
71
|
|
63
72
|
def logger_for(env)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Stalin::Adapter
|
2
|
+
# A Stalin adapter that is suitable for use in Unicorn worker processes.
|
3
|
+
class Unicorn < Rack
|
4
|
+
# Create a middleware instance.
|
5
|
+
#
|
6
|
+
# @param [#call] app inner Rack application
|
7
|
+
# @param [Integer] min lower-bound worker memory consumption before restart
|
8
|
+
# @param [Integer] max upper-bound worker memory consumption before restart
|
9
|
+
# @param [Integer] cycle how frequently to check memory consumption (# requests)
|
10
|
+
# @param [Boolean] verbose log extra information
|
11
|
+
def initialize(app, min=1024**3, max=2*1024**3, cycle=16, verbose=false)
|
12
|
+
super(app, :TERM, :QUIT, min, max, cycle, verbose)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/stalin/killer.rb
CHANGED
@@ -1,26 +1,28 @@
|
|
1
1
|
module Stalin
|
2
2
|
# Kill a process by sending SIGQUIT several times, then SIGTERM, and finally SIGKILL.
|
3
3
|
class Killer
|
4
|
-
# Number of cumulative tries
|
5
|
-
|
6
|
-
# Number of cumulative tries before we escalate to SIGKILL
|
7
|
-
|
4
|
+
# Number of cumulative shutdown tries before we escalate to abrupt-shutdown
|
5
|
+
MAX_GRACEFUL = 10
|
6
|
+
# Number of cumulative shutdown tries before we escalate to untrappable SIGKILL
|
7
|
+
MAX_ABRUPT = 15
|
8
8
|
|
9
9
|
# @param [Integer] pid target process ID
|
10
|
-
def initialize(pid)
|
11
|
-
@pid
|
12
|
-
@
|
10
|
+
def initialize(pid, graceful, abrupt)
|
11
|
+
@pid = pid
|
12
|
+
@graceful = graceful
|
13
|
+
@abrupt = abrupt
|
14
|
+
@tries = 0
|
13
15
|
end
|
14
16
|
|
15
17
|
# Try to kill the target process by sending it a shutdown signal.
|
16
18
|
#
|
17
|
-
# @return [
|
19
|
+
# @return [Symbol] name of signal that we sent
|
18
20
|
def kill
|
19
21
|
case @tries
|
20
|
-
when (0...
|
21
|
-
sig =
|
22
|
-
when (
|
23
|
-
sig =
|
22
|
+
when (0...MAX_GRACEFUL)
|
23
|
+
sig = @graceful
|
24
|
+
when (MAX_GRACEFUL...MAX_ABRUPT)
|
25
|
+
sig = @abrupt
|
24
26
|
else
|
25
27
|
sig = :KILL
|
26
28
|
end
|
@@ -30,4 +32,4 @@ module Stalin
|
|
30
32
|
sig
|
31
33
|
end
|
32
34
|
end
|
33
|
-
end
|
35
|
+
end
|
data/stalin.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: stalin 0.
|
5
|
+
# stub: stalin 0.2.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "stalin"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.2.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Tony Spataro"]
|
14
|
-
s.date = "
|
14
|
+
s.date = "2020-06-11"
|
15
15
|
s.description = "Kill Web application workers based on arbitrary conditions."
|
16
16
|
s.email = "xeger@xeger.net"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -27,7 +27,9 @@ Gem::Specification.new do |s|
|
|
27
27
|
"VERSION",
|
28
28
|
"lib/stalin.rb",
|
29
29
|
"lib/stalin/adapter.rb",
|
30
|
+
"lib/stalin/adapter/puma.rb",
|
30
31
|
"lib/stalin/adapter/rack.rb",
|
32
|
+
"lib/stalin/adapter/unicorn.rb",
|
31
33
|
"lib/stalin/killer.rb",
|
32
34
|
"lib/stalin/watcher.rb",
|
33
35
|
"lib/stalin/watcher/linux_proc_statm.rb",
|
@@ -36,7 +38,8 @@ Gem::Specification.new do |s|
|
|
36
38
|
]
|
37
39
|
s.homepage = "https://github.com/xeger/stalin"
|
38
40
|
s.licenses = ["MIT"]
|
39
|
-
s.
|
41
|
+
s.required_ruby_version = Gem::Requirement.new("~> 2.0")
|
42
|
+
s.rubygems_version = "2.5.1"
|
40
43
|
s.summary = "Kill rack"
|
41
44
|
|
42
45
|
if s.respond_to? :specification_version then
|
@@ -47,17 +50,23 @@ Gem::Specification.new do |s|
|
|
47
50
|
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
48
51
|
s.add_development_dependency(%q<sinatra>, ["~> 1.4"])
|
49
52
|
s.add_development_dependency(%q<unicorn>, ["~> 4.8"])
|
53
|
+
s.add_development_dependency(%q<thin>, ["~> 1.6"])
|
54
|
+
s.add_development_dependency(%q<puma>, ["~> 2.11"])
|
50
55
|
else
|
51
56
|
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
52
57
|
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
53
58
|
s.add_dependency(%q<sinatra>, ["~> 1.4"])
|
54
59
|
s.add_dependency(%q<unicorn>, ["~> 4.8"])
|
60
|
+
s.add_dependency(%q<thin>, ["~> 1.6"])
|
61
|
+
s.add_dependency(%q<puma>, ["~> 2.11"])
|
55
62
|
end
|
56
63
|
else
|
57
64
|
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
58
65
|
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
59
66
|
s.add_dependency(%q<sinatra>, ["~> 1.4"])
|
60
67
|
s.add_dependency(%q<unicorn>, ["~> 4.8"])
|
68
|
+
s.add_dependency(%q<thin>, ["~> 1.6"])
|
69
|
+
s.add_dependency(%q<puma>, ["~> 2.11"])
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stalin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Spataro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jeweler
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '4.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thin
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.6'
|
69
83
|
description: Kill Web application workers based on arbitrary conditions.
|
70
84
|
email: xeger@xeger.net
|
71
85
|
executables: []
|
@@ -82,7 +96,9 @@ files:
|
|
82
96
|
- VERSION
|
83
97
|
- lib/stalin.rb
|
84
98
|
- lib/stalin/adapter.rb
|
99
|
+
- lib/stalin/adapter/puma.rb
|
85
100
|
- lib/stalin/adapter/rack.rb
|
101
|
+
- lib/stalin/adapter/unicorn.rb
|
86
102
|
- lib/stalin/killer.rb
|
87
103
|
- lib/stalin/watcher.rb
|
88
104
|
- lib/stalin/watcher/linux_proc_statm.rb
|
@@ -98,17 +114,16 @@ require_paths:
|
|
98
114
|
- lib
|
99
115
|
required_ruby_version: !ruby/object:Gem::Requirement
|
100
116
|
requirements:
|
101
|
-
- - "
|
117
|
+
- - "~>"
|
102
118
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
119
|
+
version: '2.0'
|
104
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
121
|
requirements:
|
106
122
|
- - ">="
|
107
123
|
- !ruby/object:Gem::Version
|
108
124
|
version: '0'
|
109
125
|
requirements: []
|
110
|
-
|
111
|
-
rubygems_version: 2.2.2
|
126
|
+
rubygems_version: 3.1.2
|
112
127
|
signing_key:
|
113
128
|
specification_version: 4
|
114
129
|
summary: Kill rack
|