stalin 0.0.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|