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 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