stalin 0.0.2 → 0.1.0
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 +4 -4
- data/README.md +20 -6
- data/VERSION +1 -1
- data/lib/stalin/adapter/puma.rb +28 -0
- data/lib/stalin/adapter/rack.rb +51 -10
- data/lib/stalin/adapter/unicorn.rb +15 -0
- data/lib/stalin/adapter.rb +2 -0
- data/lib/stalin/killer.rb +15 -13
- data/stalin.gemspec +11 -3
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a33b561b1435c293a784b49b327fd2a9ae097a6
|
4
|
+
data.tar.gz: ac36a422c5daff8765e4ef426bae938cdf1b5229
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66788da1669aee34f376f25242b212e620f38914b92fe3b13abac429478bcd54ca2958448d282df3e9437ac7296f8cb80d4ec322e5689c33b6985c7481ff4bc4
|
7
|
+
data.tar.gz: c34ca0a488667afc8023f68bb4514cc41b6df9a59016b787ddf38ec4fcd506c28b61ef7fb8f7011d31279bebde60f9bda092b09141b0bf0134b89016894049d4
|
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,9 +18,13 @@ 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)
|
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!
|
24
28
|
|
25
29
|
# Installation
|
26
30
|
|
@@ -30,14 +34,24 @@ Just include stalin in your Gemfile.
|
|
30
34
|
|
31
35
|
# Usage
|
32
36
|
|
33
|
-
|
37
|
+
Decide on which application server you will use. Add some lines to your `config.ru` to
|
38
|
+
install a suitable Stalin middleware.
|
34
39
|
|
35
|
-
#
|
40
|
+
# Gem that kills app processes when their heap becomes too fragmented.
|
36
41
|
require 'stalin'
|
37
42
|
|
38
43
|
# Max memory size (RSS) per worker
|
39
44
|
mb = 1024**2
|
40
|
-
|
45
|
+
|
46
|
+
# Or Stalin::Adapter::Puma
|
47
|
+
use Stalin::Adapter::Unicorn, (192*mb), (256*mb)
|
48
|
+
|
49
|
+
If you instantiate Stalin::Adapter::Rack directly, you have two choices:
|
50
|
+
- Pass three parameters (app, graceful-shutdown signal and abrupt-shutdown signal) to decide on signalling behavior yourself
|
51
|
+
- Pass one parameter (app) to let Stalin decide which adapter to use based on which server is resident in memory
|
52
|
+
|
53
|
+
Instantiating the Rack middleware with one parameter is deprecated; we'd much rather you be
|
54
|
+
explicit about your application server than rely on our heuristic!
|
41
55
|
|
42
56
|
# Tuning
|
43
57
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Stalin::Adapter
|
2
|
+
# A Stalin adapter that is suitable for use in Puma worker processes. Puma uses SIGTERM
|
3
|
+
# to initiate orderly shutdown and seems to respond to SIGQUIT with an abrupt shutdown.
|
4
|
+
class Puma < Rack
|
5
|
+
# Create a middleware instance.
|
6
|
+
#
|
7
|
+
# @param [#call] app inner Rack application
|
8
|
+
# @param [Integer] min lower-bound worker memory consumption before restart
|
9
|
+
# @param [Integer] max upper-bound worker memory consumption before restart
|
10
|
+
# @param [Integer] cycle how frequently to check memory consumption (# requests)
|
11
|
+
# @param [Boolean] verbose log extra information
|
12
|
+
def initialize(app, min=1024**3, max=2*1024**3, cycle=16, verbose=false)
|
13
|
+
cli = nil
|
14
|
+
ObjectSpace.each_object(::Puma::CLI) { |o| cli = o }
|
15
|
+
|
16
|
+
raise RuntimeError, "Puma does not appear to be active; no instances of Puma::CLI reside in memory" unless cli
|
17
|
+
|
18
|
+
if cli.clustered? && cli.options[:workers] > 0
|
19
|
+
# We're clustered, so workers can SIGTERM themselves to cause a restart
|
20
|
+
super(app, :TERM, :QUIT, min, max, cycle, verbose)
|
21
|
+
else
|
22
|
+
# We're running in single mode; our one-and-only process must USR2 to restart
|
23
|
+
# itself
|
24
|
+
super(app, :USR2, :USR1, min, max, cycle, verbose)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/stalin/adapter/rack.rb
CHANGED
@@ -1,24 +1,65 @@
|
|
1
1
|
module Stalin::Adapter
|
2
2
|
# A low-tech but reliable solution that invokes stalin using Rack middleware.
|
3
|
-
# This is suitable for
|
4
|
-
#
|
3
|
+
# This is suitable for application srvers that use a master-and-workers
|
4
|
+
# process architecture, wohse workers respond to a graceful shutdown signal,
|
5
|
+
# and whose masters spawn new workers as needed.
|
6
|
+
#
|
7
|
+
# This functions as a base class for server-specific middlewares, and can be used if
|
8
|
+
# there is an app server that uses a signalling strategy not explicitly supported by Stalin.
|
5
9
|
class Rack
|
6
10
|
# Conversion constant for human-readable memory amounts in log messages.
|
7
11
|
MB = Float(1024**2)
|
8
12
|
|
13
|
+
# Construct a new middleware. If all six arguments are passed, construct an instance
|
14
|
+
# of this class; otherwise, use a heuristic to construct an instance of a suitable
|
15
|
+
# derived class.
|
16
|
+
#
|
17
|
+
# @raise [ArgumentError] if an incorrect number of arguments is passed
|
18
|
+
# @raise [RuntimeError] if 0..5 arguments and the heuristic can't figure out which signals to use
|
19
|
+
def self.new(*args)
|
20
|
+
case args.length
|
21
|
+
when 7
|
22
|
+
# Construct an instance of this (or a derived) class with signals explicitly specified.
|
23
|
+
super
|
24
|
+
when 3..6
|
25
|
+
# Warn that this shim will go away in v2
|
26
|
+
warn "Rack.new with fewer than 7 arguments is deprecated; please instantiate a derived class e.g. Unicorn.new or Puma.new"
|
27
|
+
|
28
|
+
# Use a heuristic to decide on the correct adapter and instantiate a derived class.
|
29
|
+
if defined?(::Unicorn)
|
30
|
+
middleware = Unicorn.allocate
|
31
|
+
elsif defined?(::Puma)
|
32
|
+
middleware = Puma.allocate
|
33
|
+
else
|
34
|
+
raise RuntimeError, "Cannot determine a suitable Stalin adapter; please instantiate this class with six arguments"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Initialize our new object (ugh)
|
38
|
+
middleware.instance_eval { initialize(*args) }
|
39
|
+
middleware
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Wrong number of arguments (#{args.length} for 3..7)"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
9
45
|
# Create a middleware instance.
|
10
46
|
#
|
11
|
-
# @param [#call] app
|
47
|
+
# @param [#call] app inner Rack application
|
48
|
+
# @param [Symbol] graceful name of graceful-shutdown signal
|
49
|
+
# @param [Symbol] abrupt name of abrupt-shutdown signal
|
12
50
|
# @param [Integer] min lower-bound worker memory consumption before restart
|
13
51
|
# @param [Integer] max upper-bound worker memory consumption before restart
|
14
52
|
# @param [Integer] cycle how frequently to check memory consumption (# requests)
|
15
53
|
# @param [Boolean] verbose log extra information
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
54
|
+
# @param [Array] signals pair of two Symbol signal-names: one for "graceful shutdown please" and one for "terminate immediately"
|
55
|
+
def initialize(app, graceful, abrupt, min=1024**3, max=2*1024**3, cycle=16, verbose=false)
|
56
|
+
@app = app
|
57
|
+
@graceful = graceful
|
58
|
+
@abrupt = abrupt
|
59
|
+
@min = min
|
60
|
+
@max = max
|
61
|
+
@cycle = cycle
|
62
|
+
@verbose = verbose
|
22
63
|
end
|
23
64
|
|
24
65
|
def call(env)
|
@@ -34,7 +75,7 @@ module Stalin::Adapter
|
|
34
75
|
if @req % @cycle == 0
|
35
76
|
@req = 0
|
36
77
|
@watcher ||= ::Stalin::Watcher.new(Process.pid)
|
37
|
-
@killer ||= ::Stalin::Killer.new(Process.pid)
|
78
|
+
@killer ||= ::Stalin::Killer.new(Process.pid, @graceful, @abrupt)
|
38
79
|
if (used = @watcher.watch) > @lim
|
39
80
|
sig = @killer.kill
|
40
81
|
@watcher.watch
|
@@ -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, :QUIT, :TERM, min, max, cycle, verbose)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/stalin/adapter.rb
CHANGED
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.0
|
5
|
+
# stub: stalin 0.1.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "stalin"
|
9
|
-
s.version = "0.0
|
9
|
+
s.version = "0.1.0"
|
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 = "2015-
|
14
|
+
s.date = "2015-04-17"
|
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",
|
@@ -47,17 +49,23 @@ Gem::Specification.new do |s|
|
|
47
49
|
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
48
50
|
s.add_development_dependency(%q<sinatra>, ["~> 1.4"])
|
49
51
|
s.add_development_dependency(%q<unicorn>, ["~> 4.8"])
|
52
|
+
s.add_development_dependency(%q<thin>, ["~> 1.6"])
|
53
|
+
s.add_development_dependency(%q<puma>, ["~> 2.11"])
|
50
54
|
else
|
51
55
|
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
52
56
|
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
53
57
|
s.add_dependency(%q<sinatra>, ["~> 1.4"])
|
54
58
|
s.add_dependency(%q<unicorn>, ["~> 4.8"])
|
59
|
+
s.add_dependency(%q<thin>, ["~> 1.6"])
|
60
|
+
s.add_dependency(%q<puma>, ["~> 2.11"])
|
55
61
|
end
|
56
62
|
else
|
57
63
|
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
58
64
|
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
59
65
|
s.add_dependency(%q<sinatra>, ["~> 1.4"])
|
60
66
|
s.add_dependency(%q<unicorn>, ["~> 4.8"])
|
67
|
+
s.add_dependency(%q<thin>, ["~> 1.6"])
|
68
|
+
s.add_dependency(%q<puma>, ["~> 2.11"])
|
61
69
|
end
|
62
70
|
end
|
63
71
|
|
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.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Spataro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jeweler
|
@@ -66,6 +66,34 @@ 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'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: puma
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.11'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.11'
|
69
97
|
description: Kill Web application workers based on arbitrary conditions.
|
70
98
|
email: xeger@xeger.net
|
71
99
|
executables: []
|
@@ -82,7 +110,9 @@ files:
|
|
82
110
|
- VERSION
|
83
111
|
- lib/stalin.rb
|
84
112
|
- lib/stalin/adapter.rb
|
113
|
+
- lib/stalin/adapter/puma.rb
|
85
114
|
- lib/stalin/adapter/rack.rb
|
115
|
+
- lib/stalin/adapter/unicorn.rb
|
86
116
|
- lib/stalin/killer.rb
|
87
117
|
- lib/stalin/watcher.rb
|
88
118
|
- lib/stalin/watcher/linux_proc_statm.rb
|