puma 3.7.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +229 -1
  3. data/README.md +179 -212
  4. data/docs/architecture.md +37 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +28 -0
  10. data/docs/restart.md +41 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +130 -37
  13. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  14. data/ext/puma_http11/extconf.rb +8 -0
  15. data/ext/puma_http11/http11_parser.c +84 -84
  16. data/ext/puma_http11/http11_parser.rl +9 -9
  17. data/ext/puma_http11/mini_ssl.c +105 -9
  18. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  19. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  21. data/lib/puma.rb +10 -0
  22. data/lib/puma/accept_nonblock.rb +2 -0
  23. data/lib/puma/app/status.rb +13 -0
  24. data/lib/puma/binder.rb +33 -18
  25. data/lib/puma/cli.rb +48 -33
  26. data/lib/puma/client.rb +94 -22
  27. data/lib/puma/cluster.rb +69 -21
  28. data/lib/puma/commonlogger.rb +2 -0
  29. data/lib/puma/configuration.rb +134 -136
  30. data/lib/puma/const.rb +16 -2
  31. data/lib/puma/control_cli.rb +31 -18
  32. data/lib/puma/convenient.rb +5 -3
  33. data/lib/puma/daemon_ext.rb +2 -0
  34. data/lib/puma/delegation.rb +2 -0
  35. data/lib/puma/detect.rb +2 -0
  36. data/lib/puma/dsl.rb +349 -113
  37. data/lib/puma/events.rb +8 -4
  38. data/lib/puma/io_buffer.rb +3 -6
  39. data/lib/puma/jruby_restart.rb +2 -1
  40. data/lib/puma/launcher.rb +60 -36
  41. data/lib/puma/minissl.rb +85 -28
  42. data/lib/puma/null_io.rb +2 -0
  43. data/lib/puma/plugin.rb +2 -0
  44. data/lib/puma/plugin/tmp_restart.rb +3 -2
  45. data/lib/puma/rack/builder.rb +4 -1
  46. data/lib/puma/rack/urlmap.rb +2 -0
  47. data/lib/puma/rack_default.rb +2 -0
  48. data/lib/puma/reactor.rb +218 -30
  49. data/lib/puma/runner.rb +18 -4
  50. data/lib/puma/server.rb +149 -56
  51. data/lib/puma/single.rb +16 -5
  52. data/lib/puma/state_file.rb +2 -0
  53. data/lib/puma/tcp_logger.rb +2 -0
  54. data/lib/puma/thread_pool.rb +59 -6
  55. data/lib/puma/util.rb +2 -6
  56. data/lib/rack/handler/puma.rb +58 -19
  57. data/tools/jungle/README.md +12 -2
  58. data/tools/jungle/init.d/README.md +2 -0
  59. data/tools/jungle/init.d/puma +8 -8
  60. data/tools/jungle/init.d/run-puma +1 -1
  61. data/tools/jungle/rc.d/README.md +74 -0
  62. data/tools/jungle/rc.d/puma +61 -0
  63. data/tools/jungle/rc.d/puma.conf +10 -0
  64. data/tools/trickletest.rb +1 -1
  65. metadata +25 -85
  66. data/.github/issue_template.md +0 -20
  67. data/Gemfile +0 -12
  68. data/Manifest.txt +0 -77
  69. data/Rakefile +0 -158
  70. data/gemfiles/2.1-Gemfile +0 -12
  71. data/lib/puma/compat.rb +0 -14
  72. data/lib/puma/java_io_buffer.rb +0 -45
  73. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  74. data/puma.gemspec +0 -52
data/lib/puma/single.rb CHANGED
@@ -1,13 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/runner'
2
4
  require 'puma/detect'
3
5
  require 'puma/plugin'
4
6
 
5
7
  module Puma
8
+ # This class is instantiated by the `Puma::Launcher` and used
9
+ # to boot and serve a Ruby application when no puma "workers" are needed
10
+ # i.e. only using "threaded" mode. For example `$ puma -t 1:5`
11
+ #
12
+ # At the core of this class is running an instance of `Puma::Server` which
13
+ # gets created via the `start_server` method from the `Puma::Runner` class
14
+ # that this inherits from.
6
15
  class Single < Runner
7
16
  def stats
8
- b = @server.backlog
9
- r = @server.running
10
- %Q!{ "backlog": #{b}, "running": #{r} }!
17
+ b = @server.backlog || 0
18
+ r = @server.running || 0
19
+ t = @server.pool_capacity || 0
20
+ m = @server.max_threads || 0
21
+ %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
11
22
  end
12
23
 
13
24
  def restart
@@ -15,7 +26,7 @@ module Puma
15
26
  end
16
27
 
17
28
  def stop
18
- @server.stop false
29
+ @server.stop(false) if @server
19
30
  end
20
31
 
21
32
  def halt
@@ -25,7 +36,7 @@ module Puma
25
36
  def stop_blocked
26
37
  log "- Gracefully stopping, waiting for requests to finish"
27
38
  @control.stop(true) if @control
28
- @server.stop(true)
39
+ @server.stop(true) if @server
29
40
  end
30
41
 
31
42
  def jruby_daemon?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Puma
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class TCPLogger
3
5
  def initialize(logger, app, quiet=false)
@@ -1,8 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
 
3
5
  module Puma
4
- # A simple thread pool management object.
6
+ # Internal Docs for A simple thread pool management object.
7
+ #
8
+ # Each Puma "worker" has a thread pool to process requests.
5
9
  #
10
+ # First a connection to a client is made in `Puma::Server`. It is wrapped in a
11
+ # `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
12
+ # the whole request is buffered into memory. Once the request is ready, it is passed into
13
+ # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
14
+ #
15
+ # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
16
+ # and proceses it.
6
17
  class ThreadPool
7
18
  class ForceShutdown < RuntimeError
8
19
  end
@@ -49,11 +60,11 @@ module Puma
49
60
  @clean_thread_locals = false
50
61
  end
51
62
 
52
- attr_reader :spawned, :trim_requested
63
+ attr_reader :spawned, :trim_requested, :waiting
53
64
  attr_accessor :clean_thread_locals
54
65
 
55
66
  def self.clean_thread_locals
56
- Thread.current.keys.each do |key|
67
+ Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
57
68
  Thread.current[key] = nil unless key == :__recursive_key__
58
69
  end
59
70
  end
@@ -64,6 +75,10 @@ module Puma
64
75
  @mutex.synchronize { @todo.size }
65
76
  end
66
77
 
78
+ def pool_capacity
79
+ waiting + (@max - spawned)
80
+ end
81
+
67
82
  # :nodoc:
68
83
  #
69
84
  # Must be called with @mutex held!
@@ -71,9 +86,9 @@ module Puma
71
86
  def spawn_thread
72
87
  @spawned += 1
73
88
 
74
- th = Thread.new do
89
+ th = Thread.new(@spawned) do |spawned|
75
90
  # Thread name is new in Ruby 2.3
76
- Thread.current.name = 'puma %03i' % @spawned if Thread.current.respond_to?(:name=)
91
+ Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
77
92
  todo = @todo
78
93
  block = @block
79
94
  mutex = @mutex
@@ -153,9 +168,47 @@ module Puma
153
168
  end
154
169
  end
155
170
 
171
+ # This method is used by `Puma::Server` to let the server know when
172
+ # the thread pool can pull more requests from the socket and
173
+ # pass to the reactor.
174
+ #
175
+ # The general idea is that the thread pool can only work on a fixed
176
+ # number of requests at the same time. If it is already processing that
177
+ # number of requests then it is at capacity. If another Puma process has
178
+ # spare capacity, then the request can be left on the socket so the other
179
+ # worker can pick it up and process it.
180
+ #
181
+ # For example: if there are 5 threads, but only 4 working on
182
+ # requests, this method will not wait and the `Puma::Server`
183
+ # can pull a request right away.
184
+ #
185
+ # If there are 5 threads and all 5 of them are busy, then it will
186
+ # pause here, and wait until the `not_full` condition variable is
187
+ # signaled, usually this indicates that a request has been processed.
188
+ #
189
+ # It's important to note that even though the server might accept another
190
+ # request, it might not be added to the `@todo` array right away.
191
+ # For example if a slow client has only sent a header, but not a body
192
+ # then the `@todo` array would stay the same size as the reactor works
193
+ # to try to buffer the request. In tha scenario the next call to this
194
+ # method would not block and another request would be added into the reactor
195
+ # by the server. This would continue until a fully bufferend request
196
+ # makes it through the reactor and can then be processed by the thread pool.
197
+ #
198
+ # Returns the current number of busy threads, or +nil+ if shutting down.
199
+ #
156
200
  def wait_until_not_full
157
201
  @mutex.synchronize do
158
- until @todo.size - @waiting < @max - @spawned or @shutdown
202
+ while true
203
+ return if @shutdown
204
+
205
+ # If we can still spin up new threads and there
206
+ # is work queued that cannot be handled by waiting
207
+ # threads, then accept more work until we would
208
+ # spin up the max number of threads.
209
+ busy_threads = @spawned - @waiting + @todo.size
210
+ return busy_threads if @max > busy_threads
211
+
159
212
  @not_full.wait @mutex
160
213
  end
161
214
  end
data/lib/puma/util.rb CHANGED
@@ -1,10 +1,6 @@
1
- major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
1
+ # frozen_string_literal: true
2
2
 
3
- if major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
4
- require 'puma/rack/backports/uri/common_193'
5
- else
6
- require 'uri/common'
7
- end
3
+ require 'uri/common'
8
4
 
9
5
  module Puma
10
6
  module Util
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/handler'
2
4
 
3
5
  module Rack
@@ -8,46 +10,61 @@ module Rack
8
10
  :Silent => false
9
11
  }
10
12
 
11
- def self.run(app, options = {})
13
+ def self.config(app, options = {})
14
+ require 'puma'
12
15
  require 'puma/configuration'
13
16
  require 'puma/events'
14
17
  require 'puma/launcher'
15
18
 
16
- options = DEFAULT_OPTIONS.merge(options)
19
+ default_options = DEFAULT_OPTIONS.dup
20
+
21
+ # Libraries pass in values such as :Port and there is no way to determine
22
+ # if it is a default provided by the library or a special value provided
23
+ # by the user. A special key `user_supplied_options` can be passed. This
24
+ # contains an array of all explicitly defined user options. We then
25
+ # know that all other values are defaults
26
+ if user_supplied_options = options.delete(:user_supplied_options)
27
+ (options.keys - user_supplied_options).each do |k|
28
+ default_options[k] = options.delete(k)
29
+ end
30
+ end
17
31
 
18
- conf = ::Puma::Configuration.new(options) do |c|
19
- c.quiet
32
+ conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
33
+ user_config.quiet
20
34
 
21
35
  if options.delete(:Verbose)
22
36
  app = Rack::CommonLogger.new(app, STDOUT)
23
37
  end
24
38
 
25
39
  if options[:environment]
26
- c.environment options[:environment]
40
+ user_config.environment options[:environment]
27
41
  end
28
42
 
29
43
  if options[:Threads]
30
44
  min, max = options.delete(:Threads).split(':', 2)
31
- c.threads min, max
45
+ user_config.threads min, max
32
46
  end
33
47
 
34
- host = options[:Host]
35
-
36
- if host && (host[0,1] == '.' || host[0,1] == '/')
37
- c.bind "unix://#{host}"
38
- elsif host && host =~ /^ssl:\/\//
39
- uri = URI.parse(host)
40
- uri.port ||= options[:Port] || ::Puma::Configuration::DefaultTCPPort
41
- c.bind uri.to_s
42
- else
43
- host ||= ::Puma::Configuration::DefaultTCPHost
44
- port = options[:Port] || ::Puma::Configuration::DefaultTCPPort
48
+ if options[:Host] || options[:Port]
49
+ host = options[:Host] || default_options[:Host]
50
+ port = options[:Port] || default_options[:Port]
51
+ self.set_host_port_to_config(host, port, user_config)
52
+ end
45
53
 
46
- c.port port, host
54
+ if default_options[:Host]
55
+ file_config.set_default_host(default_options[:Host])
47
56
  end
57
+ self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
48
58
 
49
- c.app app
59
+ user_config.app app
50
60
  end
61
+ conf
62
+ end
63
+
64
+
65
+
66
+ def self.run(app, options = {})
67
+ conf = self.config(app, options)
51
68
 
52
69
  events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
53
70
 
@@ -71,6 +88,28 @@ module Rack
71
88
  "Verbose" => "Don't report each request (default: false)"
72
89
  }
73
90
  end
91
+
92
+ def self.set_host_port_to_config(host, port, config)
93
+ config.clear_binds! if host || port
94
+
95
+ if host && (host[0,1] == '.' || host[0,1] == '/')
96
+ config.bind "unix://#{host}"
97
+ elsif host && host =~ /^ssl:\/\//
98
+ uri = URI.parse(host)
99
+ uri.port ||= port || ::Puma::Configuration::DefaultTCPPort
100
+ config.bind uri.to_s
101
+ else
102
+
103
+ if host
104
+ port ||= ::Puma::Configuration::DefaultTCPPort
105
+ end
106
+
107
+ if port
108
+ host ||= ::Puma::Configuration::DefaultTCPHost
109
+ config.port port, host
110
+ end
111
+ end
112
+ end
74
113
  end
75
114
 
76
115
  register :puma, Puma
@@ -1,9 +1,19 @@
1
1
  # Puma as a service
2
2
 
3
+ ## Upstart
4
+
5
+ See `/tools/jungle/upstart` for Ubuntu's upstart scripts.
6
+
7
+ ## Systemd
8
+
9
+ See [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md).
10
+
3
11
  ## Init.d
4
12
 
13
+ Deprecatation Warning : `init.d` was replaced by `systemd` since Debian 8 and Ubuntu 16.04, you should look into [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) unless you are on an older OS.
14
+
5
15
  See `/tools/jungle/init.d` for tools to use with init.d and start-stop-daemon.
6
16
 
7
- ## Upstart
17
+ ## rc.d
8
18
 
9
- See `/tools/jungle/upstart` for Ubuntu's upstart scripts.
19
+ See `/tools/jungle/rc.d` for FreeBSD's rc.d scripts
@@ -1,5 +1,7 @@
1
1
  # Puma daemon service
2
2
 
3
+ Deprecatation Warning : `init.d` was replaced by `systemd` since Debian 8 and Ubuntu 16.04, you should look into [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) unless you are on an older OS.
4
+
3
5
  Init script to manage multiple Puma servers on the same box using start-stop-daemon.
4
6
 
5
7
  ## Installation
@@ -47,11 +47,11 @@ do_start_one() {
47
47
  PIDFILE=$1/tmp/puma/pid
48
48
  if [ -e $PIDFILE ]; then
49
49
  PID=`cat $PIDFILE`
50
- # If the puma isn't running, run it, otherwise restart it.
51
- if [ "`ps -A -o pid= | grep -c $PID`" -eq 0 ]; then
52
- do_start_one_do $1
53
- else
50
+ # If the puma is running, restart it, otherwise run it.
51
+ if ps -p $PID > /dev/null; then
54
52
  do_restart_one $1
53
+ else
54
+ do_start_one_do $1
55
55
  fi
56
56
  else
57
57
  do_start_one_do $1
@@ -105,9 +105,7 @@ do_stop_one() {
105
105
  STATEFILE=$1/tmp/puma/state
106
106
  if [ -e $PIDFILE ]; then
107
107
  PID=`cat $PIDFILE`
108
- if [ "`ps -A -o pid= | grep -c $PID`" -eq 0 ]; then
109
- log_daemon_msg "---> Puma $1 isn't running."
110
- else
108
+ if ps -p $PID > /dev/null; then
111
109
  log_daemon_msg "---> About to kill PID `cat $PIDFILE`"
112
110
  if [ "$USE_LOCAL_BUNDLE" -eq 1 ]; then
113
111
  cd $1 && bundle exec pumactl --state $STATEFILE stop
@@ -116,6 +114,8 @@ do_stop_one() {
116
114
  fi
117
115
  # Many daemons don't delete their pidfiles when they exit.
118
116
  rm -f $PIDFILE $STATEFILE
117
+ else
118
+ log_daemon_msg "---> Puma $1 isn't running."
119
119
  fi
120
120
  else
121
121
  log_daemon_msg "---> No puma here..."
@@ -398,7 +398,7 @@ case "$1" in
398
398
  ;;
399
399
  remove)
400
400
  if [ "$#" -lt 2 ]; then
401
- echo "Please, specifiy the app's directory to remove."
401
+ echo "Please, specify the app's directory to remove."
402
402
  exit 1
403
403
  else
404
404
  do_remove $2
@@ -15,4 +15,4 @@ elif [ -f "$HOME/.rvm/scripts/rvm" ]; then
15
15
  fi
16
16
 
17
17
  app=$1; config=$2; log=$3;
18
- cd $app && exec bundle exec puma -C $config 2>&1 >> $log
18
+ cd $app && exec bundle exec puma -C $config >> $log 2>&1
@@ -0,0 +1,74 @@
1
+ # Puma as a service using rc.d
2
+
3
+ Manage multilpe Puma servers as services on one box using FreeBSD's rc.d service.
4
+
5
+ ## Dependencies
6
+
7
+ * `jq` - a command-line json parser is needed to parse the json in the config file
8
+
9
+ ## Installation
10
+
11
+ # Copy the puma script to the rc.d directory (make sure everyone has read/execute perms)
12
+ sudo cp puma /usr/local/etc/rc.d/
13
+
14
+ # Create an empty configuration file
15
+ sudo touch /usr/local/etc/puma.conf
16
+
17
+ # Enable the puma service
18
+ sudo echo 'puma_enable="YES"' >> /etc/rc.conf
19
+
20
+ ## Managing the jungle
21
+
22
+ Puma apps are referenced in /usr/local/etc/puma.conf by default.
23
+
24
+ Start the jungle running:
25
+
26
+ `service puma start`
27
+
28
+ This script will run at boot time.
29
+
30
+
31
+ You can also stop the jungle (stops ALL puma instances) by running:
32
+
33
+ `service puma stop`
34
+
35
+
36
+ To restart the jungle:
37
+
38
+ `service puma restart`
39
+
40
+ ## Conventions
41
+
42
+ * The script expects:
43
+ * a config file to exist under `config/puma.rb` in your app. E.g.: `/home/apps/my-app/config/puma.rb`.
44
+
45
+ You can always change those defaults by editing the scripts.
46
+
47
+ ## Here's what a minimal app's config file should have
48
+
49
+ ```
50
+ {
51
+ "servers" : [
52
+ {
53
+ "dir": "/path/to/rails/project",
54
+ "user": "deploy-user",
55
+ "ruby_version": "ruby.version",
56
+ "ruby_env": "rbenv"
57
+ }
58
+ ]
59
+ }
60
+ ```
61
+
62
+ ## Before starting...
63
+
64
+ You need to customise `puma.conf` to:
65
+
66
+ * Set the right user your app should be running on unless you want root to execute it!
67
+ * Set the directory of the app
68
+ * Set the ruby version to execute
69
+ * Set the ruby environment (currently set to rbenv, since that is the only ruby environment currently supported)
70
+ * Add additional server instances following the scheme in the example
71
+
72
+ ## Notes:
73
+
74
+ Only rbenv is currently supported.