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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1895ae07adb3fd17811e68a5762343a42a424c55
4
- data.tar.gz: d703399879596243653ed8d4b2a1380fdbc64890
2
+ SHA256:
3
+ metadata.gz: f573c1d3fa4d4b5247495ab63ef77a0c1f90471e5ab6efb232285c8321b33c82
4
+ data.tar.gz: 928cfad72a40b1f72b810520c42cc0e30a4f50d95da1cef3978892efada8dad5
5
5
  SHA512:
6
- metadata.gz: fec5772f43ae44d775b5fce828f47d64555423eb2f271bcee8a006af8aeb0c53127b6d1091b840e85442c393d7cb41f4cc65aad7a363c7e1dfcfc4bf11736948
7
- data.tar.gz: 1753c88baab75f9e0a77f1427b00f2f0d3194b973fcde1886f1d3eb4ef7d67ba7d68716d8e15595dc0696a2f4b9855d136c99d671e72623da501b95f6269bc7b
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 gradually kills your workers before they cause swapping, resulting in better
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
- (As you can see, we are far short of our _goal_ to support many servers! More to come as needed;
23
- I'm happy to take your contributions.)
23
+ Servers known NOT to work:
24
+ - Thin (no supervisor process; need an adapter that execs or something)
24
25
 
25
- # Installation
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
- # Usage
37
+ ## Usage
32
38
 
33
- Add these lines near the top of your `config.ru`
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
- # Unicorn self-process killer
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
- use Stalin::Adapter::Rack, (192*mb), (256*mb)
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
- # Tuning
83
+ bundle exec puma fixtures/puma.ru
84
+
85
+ ### Multi-Process Puma
43
86
 
44
- TODO
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.0.2
1
+ 0.2.1
@@ -5,3 +5,5 @@ module Stalin
5
5
  end
6
6
 
7
7
  require 'stalin/adapter/rack'
8
+ require 'stalin/adapter/unicorn'
9
+ require 'stalin/adapter/puma'
@@ -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
@@ -1,24 +1,29 @@
1
1
  module Stalin::Adapter
2
- # A low-tech but reliable solution that invokes stalin using Rack middleware.
3
- # This is suitable for servers that handle SIGQUIT gracefully and spawn new
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
- def initialize(app, min=1024**3, max=2*1024**3, cycle=16, verbose=false)
17
- @app = app
18
- @min = min
19
- @max = max
20
- @cycle = cycle
21
- @verbose = verbose
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
- @lim ||= @min + randomize(@max - @min + 1)
31
- @req ||= 0
32
- @req += 1
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
- @watcher.watch
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; memory usage %.1f MB < %.1f MB" %
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
- RUBY_VERSION > "1.9" ? Random.rand(integer.abs) : rand(integer)
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
@@ -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 to send SIGQUIT before we escalate to SIGTERM
5
- MAX_QUIT = 10
6
- # Number of cumulative tries before we escalate to SIGKILL
7
- MAX_TERM = 15
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 = pid
12
- @tries = 0
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 [:QUIT,:TERM,:KILL] name of signal that we sent
19
+ # @return [Symbol] name of signal that we sent
18
20
  def kill
19
21
  case @tries
20
- when (0...MAX_QUIT)
21
- sig = :QUIT
22
- when (MAX_QUIT...MAX_TERM)
23
- sig = :TERM
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
@@ -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.2 ruby lib
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.0.2"
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 = "2015-02-10"
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.rubygems_version = "2.2.2"
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.0.2
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: 2015-02-12 00:00:00.000000000 Z
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
- rubyforge_project:
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