puma 3.12.6 → 5.3.2

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1400 -451
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +20 -10
  20. data/docs/rails_dev_mode.md +29 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +7 -6
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +48 -70
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +27 -0
  28. data/ext/puma_http11/http11_parser.c +81 -108
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +3 -3
  33. data/ext/puma_http11/mini_ssl.c +254 -91
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  38. data/ext/puma_http11/puma_http11.c +34 -50
  39. data/lib/puma.rb +54 -0
  40. data/lib/puma/app/status.rb +68 -49
  41. data/lib/puma/binder.rb +191 -139
  42. data/lib/puma/cli.rb +15 -15
  43. data/lib/puma/client.rb +247 -226
  44. data/lib/puma/cluster.rb +221 -212
  45. data/lib/puma/cluster/worker.rb +183 -0
  46. data/lib/puma/cluster/worker_handle.rb +90 -0
  47. data/lib/puma/commonlogger.rb +2 -2
  48. data/lib/puma/configuration.rb +58 -51
  49. data/lib/puma/const.rb +32 -20
  50. data/lib/puma/control_cli.rb +109 -67
  51. data/lib/puma/detect.rb +24 -3
  52. data/lib/puma/dsl.rb +519 -121
  53. data/lib/puma/error_logger.rb +104 -0
  54. data/lib/puma/events.rb +55 -31
  55. data/lib/puma/io_buffer.rb +7 -5
  56. data/lib/puma/jruby_restart.rb +0 -58
  57. data/lib/puma/json.rb +96 -0
  58. data/lib/puma/launcher.rb +178 -68
  59. data/lib/puma/minissl.rb +147 -48
  60. data/lib/puma/minissl/context_builder.rb +79 -0
  61. data/lib/puma/null_io.rb +13 -1
  62. data/lib/puma/plugin.rb +6 -12
  63. data/lib/puma/plugin/tmp_restart.rb +2 -0
  64. data/lib/puma/queue_close.rb +26 -0
  65. data/lib/puma/rack/builder.rb +2 -4
  66. data/lib/puma/rack/urlmap.rb +2 -0
  67. data/lib/puma/rack_default.rb +2 -0
  68. data/lib/puma/reactor.rb +85 -316
  69. data/lib/puma/request.rb +467 -0
  70. data/lib/puma/runner.rb +31 -52
  71. data/lib/puma/server.rb +275 -726
  72. data/lib/puma/single.rb +11 -67
  73. data/lib/puma/state_file.rb +8 -3
  74. data/lib/puma/systemd.rb +46 -0
  75. data/lib/puma/thread_pool.rb +129 -81
  76. data/lib/puma/util.rb +13 -6
  77. data/lib/rack/handler/puma.rb +5 -6
  78. data/tools/Dockerfile +16 -0
  79. data/tools/trickletest.rb +0 -1
  80. metadata +45 -28
  81. data/ext/puma_http11/io_buffer.c +0 -155
  82. data/lib/puma/accept_nonblock.rb +0 -23
  83. data/lib/puma/compat.rb +0 -14
  84. data/lib/puma/convenient.rb +0 -25
  85. data/lib/puma/daemon_ext.rb +0 -33
  86. data/lib/puma/delegation.rb +0 -13
  87. data/lib/puma/java_io_buffer.rb +0 -47
  88. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,9 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "3.12.6".freeze
104
- CODE_NAME = "Llamas in Pajamas".freeze
103
+ PUMA_VERSION = VERSION = "5.3.2".freeze
104
+ CODE_NAME = "Sweetnighter".freeze
105
+
105
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
107
 
107
108
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -129,27 +130,24 @@ module Puma
129
130
  REQUEST_URI= 'REQUEST_URI'.freeze
130
131
  REQUEST_PATH = 'REQUEST_PATH'.freeze
131
132
  QUERY_STRING = 'QUERY_STRING'.freeze
133
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
132
134
 
133
135
  PATH_INFO = 'PATH_INFO'.freeze
134
136
 
135
137
  PUMA_TMP_BASE = "puma".freeze
136
138
 
137
- # Indicate that we couldn't parse the request
138
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
139
-
140
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
141
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
142
-
143
- # The standard empty 408 response for requests that timed out.
144
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
145
-
146
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
147
-
148
- # Indicate that there was an internal error, obviously.
149
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
150
-
151
- # A common header for indicating the server is too busy. Not used yet.
152
- ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
139
+ ERROR_RESPONSE = {
140
+ # Indicate that we couldn't parse the request
141
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
142
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
143
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
144
+ # The standard empty 408 response for requests that timed out.
145
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
146
+ # Indicate that there was an internal error, obviously.
147
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
148
+ # A common header for indicating the server is too busy. Not used yet.
149
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
+ }
153
151
 
154
152
  # The basic max request size we'll try to read.
155
153
  CHUNK_SIZE = 16 * 1024
@@ -167,6 +165,9 @@ module Puma
167
165
  LINE_END = "\r\n".freeze
168
166
  REMOTE_ADDR = "REMOTE_ADDR".freeze
169
167
  HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
168
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
169
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
170
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
170
171
 
171
172
  SERVER_NAME = "SERVER_NAME".freeze
172
173
  SERVER_PORT = "SERVER_PORT".freeze
@@ -175,7 +176,6 @@ module Puma
175
176
  PORT_443 = "443".freeze
176
177
  LOCALHOST = "localhost".freeze
177
178
  LOCALHOST_IP = "127.0.0.1".freeze
178
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
179
179
 
180
180
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
181
181
  HTTP_11 = "HTTP/1.1".freeze
@@ -228,12 +228,24 @@ module Puma
228
228
  COLON = ": ".freeze
229
229
 
230
230
  NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
232
231
 
233
232
  HIJACK_P = "rack.hijack?".freeze
234
233
  HIJACK = "rack.hijack".freeze
235
234
  HIJACK_IO = "rack.hijack_io".freeze
236
235
 
237
236
  EARLY_HINTS = "rack.early_hints".freeze
237
+
238
+ # Minimum interval to checks worker health
239
+ WORKER_CHECK_INTERVAL = 5
240
+
241
+ # Illegal character in the key or value of response header
242
+ DQUOTE = "\"".freeze
243
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
244
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
245
+ # header values can contain HTAB?
246
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
247
+
248
+ # Banned keys of response header
249
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
238
250
  end
239
251
  end
@@ -11,7 +11,32 @@ require 'socket'
11
11
  module Puma
12
12
  class ControlCLI
13
13
 
14
- COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
14
+ # values must be string or nil
15
+ # value of `nil` means command cannot be processed via signal
16
+ # @version 5.0.3
17
+ CMD_PATH_SIG_MAP = {
18
+ 'gc' => nil,
19
+ 'gc-stats' => nil,
20
+ 'halt' => 'SIGQUIT',
21
+ 'phased-restart' => 'SIGUSR1',
22
+ 'refork' => 'SIGURG',
23
+ 'reload-worker-directory' => nil,
24
+ 'restart' => 'SIGUSR2',
25
+ 'start' => nil,
26
+ 'stats' => nil,
27
+ 'status' => '',
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil
30
+ }.freeze
31
+
32
+ # @deprecated 6.0.0
33
+ COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
+
35
+ # commands that cannot be used in a request
36
+ NO_REQ_COMMANDS = %w{refork}.freeze
37
+
38
+ # @version 5.0.0
39
+ PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
15
40
 
16
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
42
  @state = nil
@@ -22,6 +47,7 @@ module Puma
22
47
  @control_auth_token = nil
23
48
  @config_file = nil
24
49
  @command = nil
50
+ @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
25
51
 
26
52
  @argv = argv.dup
27
53
  @stdout = stdout
@@ -29,7 +55,7 @@ module Puma
29
55
  @cli_options = {}
30
56
 
31
57
  opts = OptionParser.new do |o|
32
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
58
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
33
59
 
34
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
35
61
  @state = arg
@@ -59,13 +85,18 @@ module Puma
59
85
  @config_file = arg
60
86
  end
61
87
 
88
+ o.on "-e", "--environment ENVIRONMENT",
89
+ "The environment to run the Rack app on (default development)" do |arg|
90
+ @environment = arg
91
+ end
92
+
62
93
  o.on_tail("-H", "--help", "Show this message") do
63
94
  @stdout.puts o
64
95
  exit
65
96
  end
66
97
 
67
98
  o.on_tail("-V", "--version", "Show version") do
68
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
69
100
  exit
70
101
  end
71
102
  end
@@ -75,9 +106,22 @@ module Puma
75
106
 
76
107
  @command = argv.shift
77
108
 
109
+ # check presence of command
110
+ unless @command
111
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
112
+ end
113
+
114
+ unless CMD_PATH_SIG_MAP.key? @command
115
+ raise "Invalid command: #{@command}"
116
+ end
117
+
78
118
  unless @config_file == '-'
79
- if @config_file.nil? and File.exist?('config/puma.rb')
80
- @config_file = 'config/puma.rb'
119
+ environment = @environment || 'development'
120
+
121
+ if @config_file.nil?
122
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
123
+ File.exist?(f)
124
+ end
81
125
  end
82
126
 
83
127
  if @config_file
@@ -89,19 +133,8 @@ module Puma
89
133
  @pidfile ||= config.options[:pidfile]
90
134
  end
91
135
  end
92
-
93
- # check present of command
94
- unless @command
95
- raise "Available commands: #{COMMANDS.join(", ")}"
96
- end
97
-
98
- unless COMMANDS.include? @command
99
- raise "Invalid command: #{@command}"
100
- end
101
-
102
136
  rescue => e
103
137
  @stdout.puts e.message
104
- @stdout.puts e.backtrace
105
138
  exit 1
106
139
  end
107
140
 
@@ -123,7 +156,7 @@ module Puma
123
156
  @pid = sf.pid
124
157
  elsif @pidfile
125
158
  # get pid from pid_file
126
- @pid = File.open(@pidfile).gets.to_i
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
127
160
  end
128
161
  end
129
162
 
@@ -131,17 +164,29 @@ module Puma
131
164
  uri = URI.parse @control_url
132
165
 
133
166
  # create server object by scheme
134
- server = case uri.scheme
135
- when "tcp"
136
- TCPSocket.new uri.host, uri.port
137
- when "unix"
138
- UNIXSocket.new "#{uri.host}#{uri.path}"
139
- else
140
- raise "Invalid scheme: #{uri.scheme}"
141
- end
142
-
143
- if @command == "status"
144
- message "Puma is started"
167
+ server =
168
+ case uri.scheme
169
+ when 'ssl'
170
+ require 'openssl'
171
+ OpenSSL::SSL::SSLSocket.new(
172
+ TCPSocket.new(uri.host, uri.port),
173
+ OpenSSL::SSL::SSLContext.new)
174
+ .tap { |ssl| ssl.sync_close = true } # default is false
175
+ .tap(&:connect)
176
+ when 'tcp'
177
+ TCPSocket.new uri.host, uri.port
178
+ when 'unix'
179
+ # check for abstract UNIXSocket
180
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
182
+ else
183
+ raise "Invalid scheme: #{uri.scheme}"
184
+ end
185
+
186
+ if @command == 'status'
187
+ message 'Puma is started'
188
+ elsif NO_REQ_COMMANDS.include? @command
189
+ raise "Invalid request command: #{@command}"
145
190
  else
146
191
  url = "/#{@command}"
147
192
 
@@ -149,10 +194,10 @@ module Puma
149
194
  url = url + "?token=#{@control_auth_token}"
150
195
  end
151
196
 
152
- server << "GET #{url} HTTP/1.0\r\n\r\n"
197
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
153
198
 
154
199
  unless data = server.read
155
- raise "Server closed connection before responding"
200
+ raise 'Server closed connection before responding'
156
201
  end
157
202
 
158
203
  response = data.split("\r\n")
@@ -161,57 +206,55 @@ module Puma
161
206
  raise "Server sent empty response"
162
207
  end
163
208
 
164
- (@http,@code,@message) = response.first.split(" ",3)
209
+ @http, @code, @message = response.first.split(' ',3)
165
210
 
166
- if @code == "403"
167
- raise "Unauthorized access to server (wrong auth token)"
168
- elsif @code == "404"
211
+ if @code == '403'
212
+ raise 'Unauthorized access to server (wrong auth token)'
213
+ elsif @code == '404'
169
214
  raise "Command error: #{response.last}"
170
- elsif @code != "200"
215
+ elsif @code != '200'
171
216
  raise "Bad response from server: #{@code}"
172
217
  end
173
218
 
174
219
  message "Command #{@command} sent success"
175
- message response.last if @command == "stats" || @command == "gc-stats"
220
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
176
221
  end
177
222
  ensure
178
- server.close if server && !server.closed?
223
+ if server
224
+ if uri.scheme == 'ssl'
225
+ server.sysclose
226
+ else
227
+ server.close unless server.closed?
228
+ end
229
+ end
179
230
  end
180
231
 
181
232
  def send_signal
182
233
  unless @pid
183
- raise "Neither pid nor control url available"
234
+ raise 'Neither pid nor control url available'
184
235
  end
185
236
 
186
237
  begin
238
+ sig = CMD_PATH_SIG_MAP[@command]
187
239
 
188
- case @command
189
- when "restart"
190
- Process.kill "SIGUSR2", @pid
191
-
192
- when "halt"
193
- Process.kill "QUIT", @pid
194
-
195
- when "stop"
196
- Process.kill "SIGTERM", @pid
197
-
198
- when "stats"
199
- puts "Stats not available via pid only"
200
- return
201
-
202
- when "reload-worker-directory"
203
- puts "reload-worker-directory not available via pid only"
240
+ if sig.nil?
241
+ @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
204
243
  return
205
-
206
- when "phased-restart"
207
- Process.kill "SIGUSR1", @pid
208
-
209
- else
244
+ elsif sig.start_with? 'SIG'
245
+ Process.kill sig, @pid
246
+ elsif @command == 'status'
247
+ begin
248
+ Process.kill 0, @pid
249
+ @stdout.puts 'Puma is started'
250
+ @stdout.flush unless @stdout.sync
251
+ rescue Errno::ESRCH
252
+ raise 'Puma is not running'
253
+ end
210
254
  return
211
255
  end
212
-
213
256
  rescue SystemCallError
214
- if @command == "restart"
257
+ if @command == 'restart'
215
258
  start
216
259
  else
217
260
  raise "No pid '#{@pid}' found"
@@ -222,23 +265,21 @@ module Puma
222
265
  end
223
266
 
224
267
  def run
225
- return start if @command == "start"
226
-
268
+ return start if @command == 'start'
227
269
  prepare_configuration
228
270
 
229
- if Puma.windows?
271
+ if Puma.windows? || @control_url
230
272
  send_request
231
273
  else
232
- @control_url ? send_request : send_signal
274
+ send_signal
233
275
  end
234
276
 
235
277
  rescue => e
236
278
  message e.message
237
- message e.backtrace
238
279
  exit 1
239
280
  end
240
281
 
241
- private
282
+ private
242
283
  def start
243
284
  require 'puma/cli'
244
285
 
@@ -250,6 +291,7 @@ module Puma
250
291
  run_args += ["--control-url", @control_url] if @control_url
251
292
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
252
293
  run_args += ["-C", @config_file] if @config_file
294
+ run_args += ["-e", @environment] if @environment
253
295
 
254
296
  events = Puma::Events.new @stdout, @stderr
255
297
 
data/lib/puma/detect.rb CHANGED
@@ -1,15 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This file can be loaded independently of puma.rb, so it cannot have any code
4
+ # that assumes puma.rb is loaded.
5
+
6
+
3
7
  module Puma
4
- IS_JRUBY = defined?(JRUBY_VERSION)
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
10
+
11
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
+
13
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/ ||
14
+ IS_JRUBY && RUBY_DESCRIPTION =~ /mswin/)
15
+
16
+ # @version 5.2.0
17
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
5
18
 
6
19
  def self.jruby?
7
20
  IS_JRUBY
8
21
  end
9
22
 
10
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
11
-
12
23
  def self.windows?
13
24
  IS_WINDOWS
14
25
  end
26
+
27
+ # @version 5.0.0
28
+ def self.mri?
29
+ IS_MRI
30
+ end
31
+
32
+ # @version 5.0.0
33
+ def self.forkable?
34
+ HAS_FORK
35
+ end
15
36
  end
data/lib/puma/dsl.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'puma/const'
4
+
3
5
  module Puma
4
- # The methods that are available for use inside the config file.
6
+ # The methods that are available for use inside the configuration file.
5
7
  # These same methods are used in Puma cli and the rack handler
6
8
  # internally.
7
9
  #
@@ -12,24 +14,60 @@ module Puma
12
14
  # end
13
15
  # config.load
14
16
  #
15
- # puts config.options[:binds]
16
- # "tcp://127.0.0.1:3001"
17
+ # puts config.options[:binds] # => "tcp://127.0.0.1:3001"
17
18
  #
18
19
  # Used to load file:
19
20
  #
20
21
  # $ cat puma_config.rb
21
- # port 3002
22
+ # port 3002
23
+ #
24
+ # Resulting configuration:
22
25
  #
23
26
  # config = Configuration.new(config_file: "puma_config.rb")
24
27
  # config.load
25
28
  #
26
- # puts config.options[:binds]
27
- # # => "tcp://127.0.0.1:3002"
29
+ # puts config.options[:binds] # => "tcp://127.0.0.1:3002"
30
+ #
31
+ # You can also find many examples being used by the test suite in
32
+ # +test/config+.
28
33
  #
29
- # Detailed docs can be found in `examples/config.rb`
30
34
  class DSL
31
35
  include ConfigDefault
32
36
 
37
+ # convenience method so logic can be used in CI
38
+ # @see ssl_bind
39
+ #
40
+ def self.ssl_bind_str(host, port, opts)
41
+ verify = opts.fetch(:verify_mode, 'none').to_s
42
+
43
+ tls_str =
44
+ if opts[:no_tlsv1_1] then '&no_tlsv1_1=true'
45
+ elsif opts[:no_tlsv1] then '&no_tlsv1=true'
46
+ else ''
47
+ end
48
+
49
+ ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
50
+
51
+ if defined?(JRUBY_VERSION)
52
+ ssl_cipher_list = opts[:ssl_cipher_list] ?
53
+ "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
54
+
55
+ keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
56
+
57
+ "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
58
+ "&verify_mode=#{verify}#{tls_str}#{ca_additions}"
59
+ else
60
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ?
61
+ "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
62
+
63
+ v_flags = (ary = opts[:verification_flags]) ?
64
+ "&verification_flags=#{Array(ary).join ','}" : nil
65
+
66
+ "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
67
+ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
68
+ end
69
+ end
70
+
33
71
  def initialize(options, config)
34
72
  @config = config
35
73
  @options = options
@@ -79,8 +117,24 @@ module Puma
79
117
  @plugins << @config.load_plugin(name)
80
118
  end
81
119
 
82
- # Use +obj+ or +block+ as the Rack app. This allows a config file to
83
- # be the app itself.
120
+ # Use an object or block as the rack application. This allows the
121
+ # configuration file to be the application itself.
122
+ #
123
+ # @example
124
+ # app do |env|
125
+ # body = 'Hello, World!'
126
+ #
127
+ # [
128
+ # 200,
129
+ # {
130
+ # 'Content-Type' => 'text/plain',
131
+ # 'Content-Length' => body.length.to_s
132
+ # },
133
+ # [body]
134
+ # ]
135
+ # end
136
+ #
137
+ # @see Puma::Configuration#app
84
138
  #
85
139
  def app(obj=nil, &block)
86
140
  obj ||= block
@@ -90,9 +144,20 @@ module Puma
90
144
  @options[:app] = obj
91
145
  end
92
146
 
93
- # Start the Puma control rack app on +url+. This app can be communicated
94
- # with to control the main server.
147
+ # Start the Puma control rack application on +url+. This application can
148
+ # be communicated with to control the main server. Additionally, you can
149
+ # provide an authentication token, so all requests to the control server
150
+ # will need to include that token as a query parameter. This allows for
151
+ # simple authentication.
152
+ #
153
+ # Check out {Puma::App::Status} to see what the app has available.
95
154
  #
155
+ # @example
156
+ # activate_control_app 'unix:///var/run/pumactl.sock'
157
+ # @example
158
+ # activate_control_app 'unix:///var/run/pumactl.sock', { auth_token: '12345' }
159
+ # @example
160
+ # activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true }
96
161
  def activate_control_app(url="auto", opts={})
97
162
  if url == "auto"
98
163
  path = Configuration.temp_path
@@ -103,7 +168,12 @@ module Puma
103
168
  end
104
169
 
105
170
  if opts[:no_token]
106
- auth_token = :none
171
+ # We need to use 'none' rather than :none because this value will be
172
+ # passed on to an instance of OptionParser, which doesn't support
173
+ # symbols as option values.
174
+ #
175
+ # See: https://github.com/puma/puma/issues/1193#issuecomment-305995488
176
+ auth_token = 'none'
107
177
  else
108
178
  auth_token = opts[:auth_token]
109
179
  auth_token ||= Configuration.random_token
@@ -120,22 +190,32 @@ module Puma
120
190
  @options[:config_files] << file
121
191
  end
122
192
 
123
- # Adds a binding for the server to +url+. tcp://, unix://, and ssl:// are the only accepted
124
- # protocols. Use query parameters within the url to specify options.
193
+ # Bind the server to +url+. "tcp://", "unix://" and "ssl://" are the only
194
+ # accepted protocols. Multiple urls can be bound to, calling `bind` does
195
+ # not overwrite previous bindings.
125
196
  #
126
- # @note multiple urls can be bound to, calling `bind` does not overwrite previous bindings.
197
+ # The default is "tcp://0.0.0.0:9292".
127
198
  #
128
- # @example Explicitly the socket backlog depth (default is 1024)
129
- # bind('unix:///var/run/puma.sock?backlog=2048')
199
+ # You can use query parameters within the url to specify options:
130
200
  #
131
- # @example Set up ssl cert
132
- # bind('ssl://127.0.0.1:9292?key=key.key&cert=cert.pem')
201
+ # * Set the socket backlog depth with +backlog+, default is 1024.
202
+ # * Set up an SSL certificate with +key+ & +cert+.
203
+ # * Set whether to optimize for low latency instead of throughput with
204
+ # +low_latency+, default is to not optimize for low latency. This is done
205
+ # via +Socket::TCP_NODELAY+.
206
+ # * Set socket permissions with +umask+.
133
207
  #
134
- # @example Prefer low-latency over higher throughput (via `Socket::TCP_NODELAY`)
135
- # bind('tcp://0.0.0.0:9292?low_latency=true')
208
+ # @example Backlog depth
209
+ # bind 'unix:///var/run/puma.sock?backlog=512'
210
+ # @example SSL cert
211
+ # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
212
+ # @example Disable optimization for low latency
213
+ # bind 'tcp://0.0.0.0:9292?low_latency=false'
214
+ # @example Socket permissions
215
+ # bind 'unix:///var/run/puma.sock?umask=0111'
216
+ # @see Puma::Runner#load_and_bind
217
+ # @see Puma::Cluster#run
136
218
  #
137
- # @example Set socket permissions
138
- # bind('unix:///var/run/puma.sock?umask=0111')
139
219
  def bind(url)
140
220
  @options[:binds] ||= []
141
221
  @options[:binds] << url
@@ -145,48 +225,75 @@ module Puma
145
225
  @options[:binds] = []
146
226
  end
147
227
 
228
+ # Bind to (systemd) activated sockets, regardless of configured binds.
229
+ #
230
+ # Systemd can present sockets as file descriptors that are already opened.
231
+ # By default Puma will use these but only if it was explicitly told to bind
232
+ # to the socket. If not, it will close the activated sockets. This means
233
+ # all configuration is duplicated.
234
+ #
235
+ # Binds can contain additional configuration, but only SSL config is really
236
+ # relevant since the unix and TCP socket options are ignored.
237
+ #
238
+ # This means there is a lot of duplicated configuration for no additional
239
+ # value in most setups. This method tells the launcher to bind to all
240
+ # activated sockets, regardless of existing bind.
241
+ #
242
+ # To clear configured binds, the value only can be passed. This will clear
243
+ # out any binds that may have been configured.
244
+ #
245
+ # @example Use any systemd activated sockets as well as configured binds
246
+ # bind_to_activated_sockets
247
+ #
248
+ # @example Only bind to systemd activated sockets, ignoring other binds
249
+ # bind_to_activated_sockets 'only'
250
+ def bind_to_activated_sockets(bind=true)
251
+ @options[:bind_to_activated_sockets] = bind
252
+ end
253
+
148
254
  # Define the TCP port to bind to. Use +bind+ for more advanced options.
149
255
  #
256
+ # @example
257
+ # port 9292
150
258
  def port(port, host=nil)
151
259
  host ||= default_host
152
- bind "tcp://#{host}:#{port}"
260
+ bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
153
261
  end
154
262
 
155
- # Define how long persistent connections can be idle before puma closes
156
- # them
157
- #
263
+ # Define how long persistent connections can be idle before Puma closes them.
264
+ # @see Puma::Server.new
158
265
  def persistent_timeout(seconds)
159
266
  @options[:persistent_timeout] = Integer(seconds)
160
267
  end
161
268
 
162
- # Define how long the tcp socket stays open, if no data has been received
163
- #
269
+ # Define how long the tcp socket stays open, if no data has been received.
270
+ # @see Puma::Server.new
164
271
  def first_data_timeout(seconds)
165
272
  @options[:first_data_timeout] = Integer(seconds)
166
273
  end
167
274
 
168
275
  # Work around leaky apps that leave garbage in Thread locals
169
- # across requests
170
- #
276
+ # across requests.
171
277
  def clean_thread_locals(which=true)
172
278
  @options[:clean_thread_locals] = which
173
279
  end
174
280
 
175
- # Daemonize the server into the background. Highly suggest that
176
- # this be combined with +pidfile+ and +stdout_redirect+.
177
- def daemonize(which=true)
178
- @options[:daemon] = which
179
- end
180
-
181
- # When shutting down, drain the accept socket of pending
182
- # connections and process them. This loops over the accept
183
- # socket until there are no more read events and then stops
184
- # looking and waits for the requests to finish.
281
+ # When shutting down, drain the accept socket of pending connections and
282
+ # process them. This loops over the accept socket until there are no more
283
+ # read events and then stops looking and waits for the requests to finish.
284
+ # @see Puma::Server#graceful_shutdown
285
+ #
185
286
  def drain_on_shutdown(which=true)
186
287
  @options[:drain_on_shutdown] = which
187
288
  end
188
289
 
189
- # Set the environment in which the Rack's app will run.
290
+ # Set the environment in which the rack's app will run. The value must be
291
+ # a string.
292
+ #
293
+ # The default is "development".
294
+ #
295
+ # @example
296
+ # environment 'production'
190
297
  def environment(environment)
191
298
  @options[:environment] = environment
192
299
  end
@@ -198,6 +305,7 @@ module Puma
198
305
  #
199
306
  # Puma always waits a few seconds after killing a thread for it to try
200
307
  # to finish up it's work, even in :immediately mode.
308
+ # @see Puma::Server#graceful_shutdown
201
309
  def force_shutdown_after(val=:forever)
202
310
  i = case val
203
311
  when :forever
@@ -205,37 +313,48 @@ module Puma
205
313
  when :immediately
206
314
  0
207
315
  else
208
- Integer(val)
316
+ Float(val)
209
317
  end
210
318
 
211
319
  @options[:force_shutdown_after] = i
212
320
  end
213
321
 
214
322
  # Code to run before doing a restart. This code should
215
- # close logfiles, database connections, etc.
323
+ # close log files, database connections, etc.
216
324
  #
217
325
  # This can be called multiple times to add code each time.
218
326
  #
327
+ # @example
328
+ # on_restart do
329
+ # puts 'On restart...'
330
+ # end
219
331
  def on_restart(&block)
220
332
  @options[:on_restart] ||= []
221
333
  @options[:on_restart] << block
222
334
  end
223
335
 
224
- # Command to use to restart puma. This should be just how to
225
- # load puma itself (ie. 'ruby -Ilib bin/puma'), not the arguments
226
- # to puma, as those are the same as the original process.
336
+ # Command to use to restart Puma. This should be just how to
337
+ # load Puma itself (ie. 'ruby -Ilib bin/puma'), not the arguments
338
+ # to Puma, as those are the same as the original process.
227
339
  #
340
+ # @example
341
+ # restart_command '/u/app/lolcat/bin/restart_puma'
228
342
  def restart_command(cmd)
229
343
  @options[:restart_cmd] = cmd.to_s
230
344
  end
231
345
 
232
- # Store the pid of the server in the file at +path+.
346
+ # Store the pid of the server in the file at "path".
347
+ #
348
+ # @example
349
+ # pidfile '/u/apps/lolcat/tmp/pids/puma.pid'
233
350
  def pidfile(path)
234
351
  @options[:pidfile] = path.to_s
235
352
  end
236
353
 
237
- # Disable request logging.
354
+ # Disable request logging, if this isn't used it'll be enabled by default.
238
355
  #
356
+ # @example
357
+ # quiet
239
358
  def quiet(which=true)
240
359
  @options[:log_requests] = !which
241
360
  end
@@ -254,30 +373,47 @@ module Puma
254
373
 
255
374
  # Load +path+ as a rackup file.
256
375
  #
257
- def rackup(path)
258
- @options[:rackup] = path.to_s
259
- end
260
-
261
- # Run Puma in TCP mode
376
+ # The default is "config.ru".
262
377
  #
263
- def tcp_mode!
264
- @options[:mode] = :tcp
378
+ # @example
379
+ # rackup '/u/apps/lolcat/config.ru'
380
+ def rackup(path)
381
+ @options[:rackup] ||= path.to_s
265
382
  end
266
383
 
267
384
  def early_hints(answer=true)
268
385
  @options[:early_hints] = answer
269
386
  end
270
387
 
271
- # Redirect STDOUT and STDERR to files specified.
388
+ # Redirect +STDOUT+ and +STDERR+ to files specified. The +append+ parameter
389
+ # specifies whether the output is appended, the default is +false+.
390
+ #
391
+ # @example
392
+ # stdout_redirect '/app/lolcat/log/stdout', '/app/lolcat/log/stderr'
393
+ # @example
394
+ # stdout_redirect '/app/lolcat/log/stdout', '/app/lolcat/log/stderr', true
272
395
  def stdout_redirect(stdout=nil, stderr=nil, append=false)
273
396
  @options[:redirect_stdout] = stdout
274
397
  @options[:redirect_stderr] = stderr
275
398
  @options[:redirect_append] = append
276
399
  end
277
400
 
401
+ def log_formatter(&block)
402
+ @options[:log_formatter] = block
403
+ end
404
+
278
405
  # Configure +min+ to be the minimum number of threads to use to answer
279
406
  # requests and +max+ the maximum.
280
407
  #
408
+ # The default is the environment variables +PUMA_MIN_THREADS+ / +PUMA_MAX_THREADS+
409
+ # (or +MIN_THREADS+ / +MAX_THREADS+ if the +PUMA_+ variables aren't set).
410
+ #
411
+ # If these environment variables aren't set, the default is "0, 5" in MRI or "0, 16" for other interpreters.
412
+ #
413
+ # @example
414
+ # threads 0, 16
415
+ # @example
416
+ # threads 5, 5
281
417
  def threads(min, max)
282
418
  min = Integer(min)
283
419
  max = Integer(max)
@@ -293,81 +429,153 @@ module Puma
293
429
  @options[:max_threads] = max
294
430
  end
295
431
 
432
+ # Instead of `bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'` you
433
+ # can also use the this method.
434
+ #
435
+ # @example
436
+ # ssl_bind '127.0.0.1', '9292', {
437
+ # cert: path_to_cert,
438
+ # key: path_to_key,
439
+ # ssl_cipher_filter: cipher_filter, # optional
440
+ # verify_mode: verify_mode, # default 'none'
441
+ # verification_flags: flags, # optional, not supported by JRuby
442
+ # }
443
+ # @example For JRuby, two keys are required: keystore & keystore_pass.
444
+ # ssl_bind '127.0.0.1', '9292', {
445
+ # keystore: path_to_keystore,
446
+ # keystore_pass: password,
447
+ # ssl_cipher_list: cipher_list, # optional
448
+ # verify_mode: verify_mode # default 'none'
449
+ # }
296
450
  def ssl_bind(host, port, opts)
297
- verify = opts.fetch(:verify_mode, 'none')
298
-
299
- if defined?(JRUBY_VERSION)
300
- keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
301
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}"
302
- else
303
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&verify_mode=#{verify}"
304
- end
451
+ bind self.class.ssl_bind_str(host, port, opts)
305
452
  end
306
453
 
307
454
  # Use +path+ as the file to store the server info state. This is
308
- # used by pumactl to query and control the server.
455
+ # used by +pumactl+ to query and control the server.
309
456
  #
457
+ # @example
458
+ # state_path '/u/apps/lolcat/tmp/pids/puma.state'
310
459
  def state_path(path)
311
460
  @options[:state] = path.to_s
312
461
  end
313
462
 
314
- # *Cluster mode only* How many worker processes to run.
463
+ # Use +permission+ to restrict permissions for the state file.
464
+ #
465
+ # @example
466
+ # state_permission 0600
467
+ # @version 5.0.0
468
+ #
469
+ def state_permission(permission)
470
+ @options[:state_permission] = permission
471
+ end
472
+
473
+ # How many worker processes to run. Typically this is set to
474
+ # the number of available cores.
315
475
  #
476
+ # The default is the value of the environment variable +WEB_CONCURRENCY+ if
477
+ # set, otherwise 0.
478
+ #
479
+ # @note Cluster mode only.
480
+ # @see Puma::Cluster
316
481
  def workers(count)
317
482
  @options[:workers] = count.to_i
318
483
  end
319
484
 
320
- # *Cluster mode only* Code to run immediately before master process
485
+ # Disable warning message when running in cluster mode with a single worker.
486
+ #
487
+ # Cluster mode has some overhead of running an additional 'control' process
488
+ # in order to manage the cluster. If only running a single worker it is
489
+ # likely not worth paying that overhead vs running in single mode with
490
+ # additional threads instead.
491
+ #
492
+ # There are some scenarios where running cluster mode with a single worker
493
+ # may still be warranted and valid under certain deployment scenarios, see
494
+ # https://github.com/puma/puma/issues/2534
495
+ #
496
+ # Moving from workers = 1 to workers = 0 will save 10-30% of memory use.
497
+ #
498
+ # @note Cluster mode only.
499
+ def silence_single_worker_warning
500
+ @options[:silence_single_worker_warning] = true
501
+ end
502
+
503
+ # Code to run immediately before master process
321
504
  # forks workers (once on boot). These hooks can block if necessary
322
- # to wait for background operations unknown to puma to finish before
505
+ # to wait for background operations unknown to Puma to finish before
323
506
  # the process terminates.
324
- # This can be used to close any connections to remote servers (database, redis, ...)
325
- # that were opened when preloading the code
507
+ # This can be used to close any connections to remote servers (database,
508
+ # Redis, ...) that were opened when preloading the code.
326
509
  #
327
- # This can be called multiple times to add hooks.
510
+ # This can be called multiple times to add several hooks.
328
511
  #
512
+ # @note Cluster mode only.
513
+ # @example
514
+ # before_fork do
515
+ # puts "Starting workers..."
516
+ # end
329
517
  def before_fork(&block)
330
518
  @options[:before_fork] ||= []
331
519
  @options[:before_fork] << block
332
520
  end
333
521
 
334
- # *Cluster mode only* Code to run in a worker when it boots to setup
522
+ # Code to run in a worker when it boots to setup
335
523
  # the process before booting the app.
336
524
  #
337
- # This can be called multiple times to add hooks.
525
+ # This can be called multiple times to add several hooks.
338
526
  #
527
+ # @note Cluster mode only.
528
+ # @example
529
+ # on_worker_boot do
530
+ # puts 'Before worker boot...'
531
+ # end
339
532
  def on_worker_boot(&block)
340
533
  @options[:before_worker_boot] ||= []
341
534
  @options[:before_worker_boot] << block
342
535
  end
343
536
 
344
- # *Cluster mode only* Code to run immediately before a worker shuts
537
+ # Code to run immediately before a worker shuts
345
538
  # down (after it has finished processing HTTP requests). These hooks
346
539
  # can block if necessary to wait for background operations unknown
347
- # to puma to finish before the process terminates.
540
+ # to Puma to finish before the process terminates.
348
541
  #
349
- # This can be called multiple times to add hooks.
542
+ # This can be called multiple times to add several hooks.
350
543
  #
544
+ # @note Cluster mode only.
545
+ # @example
546
+ # on_worker_shutdown do
547
+ # puts 'On worker shutdown...'
548
+ # end
351
549
  def on_worker_shutdown(&block)
352
550
  @options[:before_worker_shutdown] ||= []
353
551
  @options[:before_worker_shutdown] << block
354
552
  end
355
553
 
356
- # *Cluster mode only* Code to run in the master when it is
357
- # about to create the worker by forking itself.
554
+ # Code to run in the master right before a worker is started. The worker's
555
+ # index is passed as an argument.
358
556
  #
359
- # This can be called multiple times to add hooks.
557
+ # This can be called multiple times to add several hooks.
360
558
  #
559
+ # @note Cluster mode only.
560
+ # @example
561
+ # on_worker_fork do
562
+ # puts 'Before worker fork...'
563
+ # end
361
564
  def on_worker_fork(&block)
362
565
  @options[:before_worker_fork] ||= []
363
566
  @options[:before_worker_fork] << block
364
567
  end
365
568
 
366
- # *Cluster mode only* Code to run in the master after it starts
367
- # a worker.
569
+ # Code to run in the master after a worker has been started. The worker's
570
+ # index is passed as an argument.
368
571
  #
369
- # This can be called multiple times to add hooks.
572
+ # This is called everytime a worker is to be started.
370
573
  #
574
+ # @note Cluster mode only.
575
+ # @example
576
+ # after_worker_fork do
577
+ # puts 'After worker fork...'
578
+ # end
371
579
  def after_worker_fork(&block)
372
580
  @options[:after_worker_fork] ||= []
373
581
  @options[:after_worker_fork] = block
@@ -375,33 +583,70 @@ module Puma
375
583
 
376
584
  alias_method :after_worker_boot, :after_worker_fork
377
585
 
378
- # The directory to operate out of.
379
- def directory(dir)
380
- @options[:directory] = dir.to_s
586
+ # When `fork_worker` is enabled, code to run in Worker 0
587
+ # before all other workers are re-forked from this process,
588
+ # after the server has temporarily stopped serving requests
589
+ # (once per complete refork cycle).
590
+ #
591
+ # This can be used to trigger extra garbage-collection to maximize
592
+ # copy-on-write efficiency, or close any connections to remote servers
593
+ # (database, Redis, ...) that were opened while the server was running.
594
+ #
595
+ # This can be called multiple times to add several hooks.
596
+ #
597
+ # @note Cluster mode with `fork_worker` enabled only.
598
+ # @example
599
+ # on_refork do
600
+ # 3.times {GC.start}
601
+ # end
602
+ # @version 5.0.0
603
+ #
604
+ def on_refork(&block)
605
+ @options[:before_refork] ||= []
606
+ @options[:before_refork] << block
381
607
  end
382
608
 
383
- # DEPRECATED: The directory to operate out of.
384
- def worker_directory(dir)
385
- $stderr.puts "worker_directory is deprecated. Please use `directory`"
386
- directory dir
609
+ # Code to run out-of-band when the worker is idle.
610
+ # These hooks run immediately after a request has finished
611
+ # processing and there are no busy threads on the worker.
612
+ # The worker doesn't accept new requests until this code finishes.
613
+ #
614
+ # This hook is useful for running out-of-band garbage collection
615
+ # or scheduling asynchronous tasks to execute after a response.
616
+ #
617
+ # This can be called multiple times to add several hooks.
618
+ def out_of_band(&block)
619
+ @options[:out_of_band] ||= []
620
+ @options[:out_of_band] << block
387
621
  end
388
622
 
389
- # Run the app as a raw TCP app instead of an HTTP rack app
390
- def tcp_mode
391
- @options[:mode] = :tcp
623
+ # The directory to operate out of.
624
+ #
625
+ # The default is the current directory.
626
+ #
627
+ # @example
628
+ # directory '/u/apps/lolcat'
629
+ def directory(dir)
630
+ @options[:directory] = dir.to_s
392
631
  end
393
632
 
394
- # *Cluster mode only* Preload the application before starting
395
- # the workers and setting up the listen ports. This conflicts
396
- # with using the phased restart feature, you can't use both.
633
+ # Preload the application before starting the workers; this conflicts with
634
+ # phased restart feature. On by default if your app uses more than 1 worker.
397
635
  #
636
+ # @note Cluster mode only.
637
+ # @example
638
+ # preload_app!
398
639
  def preload_app!(answer=true)
399
640
  @options[:preload_app] = answer
400
641
  end
401
642
 
402
- # Use +obj+ or +block+ as the low level error handler. This allows a config file to
403
- # change the default error on the server.
643
+ # Use +obj+ or +block+ as the low level error handler. This allows the
644
+ # configuration file to change the default error on the server.
404
645
  #
646
+ # @example
647
+ # lowlevel_error_handler do |err|
648
+ # [200, {}, ["error page"]]
649
+ # end
405
650
  def lowlevel_error_handler(obj=nil, &block)
406
651
  obj ||= block
407
652
  raise "Provide either a #call'able or a block" unless obj
@@ -411,38 +656,112 @@ module Puma
411
656
  # This option is used to allow your app and its gems to be
412
657
  # properly reloaded when not using preload.
413
658
  #
414
- # When set, if puma detects that it's been invoked in the
659
+ # When set, if Puma detects that it's been invoked in the
415
660
  # context of Bundler, it will cleanup the environment and
416
661
  # re-run itself outside the Bundler environment, but directly
417
662
  # using the files that Bundler has setup.
418
663
  #
419
- # This means that puma is now decoupled from your Bundler
664
+ # This means that Puma is now decoupled from your Bundler
420
665
  # context and when each worker loads, it will be loading a
421
666
  # new Bundler context and thus can float around as the release
422
667
  # dictates.
668
+ #
669
+ # @see extra_runtime_dependencies
670
+ #
671
+ # @note This is incompatible with +preload_app!+.
672
+ # @note This is only supported for RubyGems 2.2+
423
673
  def prune_bundler(answer=true)
424
674
  @options[:prune_bundler] = answer
425
675
  end
426
676
 
427
- # Additional text to display in process listing
677
+ # By default, Puma will raise SignalException when SIGTERM is received. In
678
+ # environments where SIGTERM is something expected, you can suppress these
679
+ # with this option.
680
+ #
681
+ # This can be useful for example in Kubernetes, where rolling restart is
682
+ # guaranteed usually on infrastructure level.
683
+ #
684
+ # @example
685
+ # raise_exception_on_sigterm false
686
+ # @see Puma::Launcher#setup_signals
687
+ # @see Puma::Cluster#setup_signals
688
+ #
689
+ def raise_exception_on_sigterm(answer=true)
690
+ @options[:raise_exception_on_sigterm] = answer
691
+ end
692
+
693
+ # When using prune_bundler, if extra runtime dependencies need to be loaded to
694
+ # initialize your app, then this setting can be used. This includes any Puma plugins.
695
+ #
696
+ # Before bundler is pruned, the gem names supplied will be looked up in the bundler
697
+ # context and then loaded again after bundler is pruned.
698
+ # Only applies if prune_bundler is used.
699
+ #
700
+ # @example
701
+ # extra_runtime_dependencies ['gem_name_1', 'gem_name_2']
702
+ # @example
703
+ # extra_runtime_dependencies ['puma_worker_killer', 'puma-heroku']
704
+ # @see Puma::Launcher#extra_runtime_deps_directories
705
+ #
706
+ def extra_runtime_dependencies(answer = [])
707
+ @options[:extra_runtime_dependencies] = Array(answer)
708
+ end
709
+
710
+ # Additional text to display in process listing.
711
+ #
712
+ # If you do not specify a tag, Puma will infer it. If you do not want Puma
713
+ # to add a tag, use an empty string.
714
+ #
715
+ # @example
716
+ # tag 'app name'
717
+ # @example
718
+ # tag ''
428
719
  def tag(string)
429
720
  @options[:tag] = string.to_s
430
721
  end
431
722
 
432
- # *Cluster mode only* Set the timeout for workers in seconds
433
- # When set the master process will terminate any workers
434
- # that have not checked in within the given +timeout+.
435
- # This mitigates hung processes. Default value is 60 seconds.
723
+ # Verifies that all workers have checked in to the master process within
724
+ # the given timeout. If not the worker process will be restarted. This is
725
+ # not a request timeout, it is to protect against a hung or dead process.
726
+ # Setting this value will not protect against slow requests.
727
+ #
728
+ # The minimum value is 6 seconds, the default value is 60 seconds.
729
+ #
730
+ # @note Cluster mode only.
731
+ # @example
732
+ # worker_timeout 60
733
+ # @see Puma::Cluster::Worker#ping_timeout
734
+ #
436
735
  def worker_timeout(timeout)
437
- @options[:worker_timeout] = Integer(timeout)
736
+ timeout = Integer(timeout)
737
+ min = Const::WORKER_CHECK_INTERVAL
738
+
739
+ if timeout <= min
740
+ raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
741
+ end
742
+
743
+ @options[:worker_timeout] = timeout
438
744
  end
439
745
 
440
- # *Cluster mode only* Set the timeout for workers to boot
746
+ # Change the default worker timeout for booting.
747
+ #
748
+ # If unspecified, this defaults to the value of worker_timeout.
749
+ #
750
+ # @note Cluster mode only.
751
+ #
752
+ # @example
753
+ # worker_boot_timeout 60
754
+ # @see Puma::Cluster::Worker#ping_timeout
755
+ #
441
756
  def worker_boot_timeout(timeout)
442
757
  @options[:worker_boot_timeout] = Integer(timeout)
443
758
  end
444
759
 
445
- # *Cluster mode only* Set the timeout for worker shutdown
760
+ # Set the timeout for worker shutdown.
761
+ #
762
+ # @note Cluster mode only.
763
+ # @see Puma::Cluster::Worker#term
764
+ #
446
765
  def worker_shutdown_timeout(timeout)
447
766
  @options[:worker_shutdown_timeout] = Integer(timeout)
448
767
  end
@@ -459,7 +778,8 @@ module Puma
459
778
  # Note that setting this to false disables HTTP keepalive and
460
779
  # slow clients will occupy a handler thread while the request
461
780
  # is being sent. A reverse proxy, such as nginx, can handle
462
- # slow clients and queue requests before they reach puma.
781
+ # slow clients and queue requests before they reach Puma.
782
+ # @see Puma::Server
463
783
  def queue_requests(answer=true)
464
784
  @options[:queue_requests] = answer
465
785
  end
@@ -467,10 +787,25 @@ module Puma
467
787
  # When a shutdown is requested, the backtraces of all the
468
788
  # threads will be written to $stdout. This can help figure
469
789
  # out why shutdown is hanging.
790
+ #
470
791
  def shutdown_debug(val=true)
471
792
  @options[:shutdown_debug] = val
472
793
  end
473
794
 
795
+
796
+ # Attempts to route traffic to less-busy workers by causing them to delay
797
+ # listening on the socket, allowing workers which are not processing any
798
+ # requests to pick up new requests first.
799
+ #
800
+ # Only works on MRI. For all other interpreters, this setting does nothing.
801
+ # @see Puma::Server#handle_servers
802
+ # @see Puma::ThreadPool#wait_for_less_busy_worker
803
+ # @version 5.0.0
804
+ #
805
+ def wait_for_less_busy_worker(val=0.005)
806
+ @options[:wait_for_less_busy_worker] = val.to_f
807
+ end
808
+
474
809
  # Control how the remote address of the connection is set. This
475
810
  # is configurable because to calculate the true socket peer address
476
811
  # a kernel syscall is required which for very fast rack handlers
@@ -478,18 +813,18 @@ module Puma
478
813
  #
479
814
  # There are 4 possible values:
480
815
  #
481
- # * :socket (the default) - read the peername from the socket using the
482
- # syscall. This is the normal behavior.
483
- # * :localhost - set the remote address to "127.0.0.1"
484
- # * header: http_header - set the remote address to the value of the
485
- # provided http header. For instance:
486
- # `set_remote_address header: "X-Real-IP"`.
487
- # Only the first word (as separated by spaces or comma)
488
- # is used, allowing headers such as X-Forwarded-For
489
- # to be used as well.
490
- # * Any string - this allows you to hardcode remote address to any value
491
- # you wish. Because puma never uses this field anyway, it's
492
- # format is entirely in your hands.
816
+ # 1. **:socket** (the default) - read the peername from the socket using the
817
+ # syscall. This is the normal behavior.
818
+ # 2. **:localhost** - set the remote address to "127.0.0.1"
819
+ # 3. **header: <http_header>**- set the remote address to the value of the
820
+ # provided http header. For instance:
821
+ # `set_remote_address header: "X-Real-IP"`.
822
+ # Only the first word (as separated by spaces or comma) is used, allowing
823
+ # headers such as X-Forwarded-For to be used as well.
824
+ # 4. **\<Any string\>** - this allows you to hardcode remote address to any value
825
+ # you wish. Because Puma never uses this field anyway, it's format is
826
+ # entirely in your hands.
827
+ #
493
828
  def set_remote_address(val=:socket)
494
829
  case val
495
830
  when :socket
@@ -512,5 +847,68 @@ module Puma
512
847
  end
513
848
  end
514
849
 
850
+ # When enabled, workers will be forked from worker 0 instead of from the master process.
851
+ # This option is similar to `preload_app` because the app is preloaded before forking,
852
+ # but it is compatible with phased restart.
853
+ #
854
+ # This option also enables the `refork` command (SIGURG), which optimizes copy-on-write performance
855
+ # in a running app.
856
+ #
857
+ # A refork will automatically trigger once after the specified number of requests
858
+ # (default 1000), or pass 0 to disable auto refork.
859
+ #
860
+ # @note Cluster mode only.
861
+ # @version 5.0.0
862
+ #
863
+ def fork_worker(after_requests=1000)
864
+ @options[:fork_worker] = Integer(after_requests)
865
+ end
866
+
867
+ # When enabled, Puma will GC 4 times before forking workers.
868
+ # If available (Ruby 2.7+), we will also call GC.compact.
869
+ # Not recommended for non-MRI Rubies.
870
+ #
871
+ # Based on the work of Koichi Sasada and Aaron Patterson, this option may
872
+ # decrease memory utilization of preload-enabled cluster-mode Pumas. It will
873
+ # also increase time to boot and fork. See your logs for details on how much
874
+ # time this adds to your boot process. For most apps, it will be less than one
875
+ # second.
876
+ #
877
+ # @see Puma::Cluster#nakayoshi_gc
878
+ # @version 5.0.0
879
+ #
880
+ def nakayoshi_fork(enabled=true)
881
+ @options[:nakayoshi_fork] = enabled
882
+ end
883
+
884
+ # The number of requests to attempt inline before sending a client back to
885
+ # the reactor to be subject to normal ordering.
886
+ #
887
+ def max_fast_inline(num_of_requests)
888
+ @options[:max_fast_inline] = Float(num_of_requests)
889
+ end
890
+
891
+ # Specify the backend for the IO selector.
892
+ #
893
+ # Provided values will be passed directly to +NIO::Selector.new+, with the
894
+ # exception of +:auto+ which will let nio4r choose the backend.
895
+ #
896
+ # Check the documentation of +NIO::Selector.backends+ for the list of valid
897
+ # options. Note that the available options on your system will depend on the
898
+ # operating system. If you want to use the pure Ruby backend (not
899
+ # recommended due to its comparatively low performance), set environment
900
+ # variable +NIO4R_PURE+ to +true+.
901
+ #
902
+ # The default is +:auto+.
903
+ #
904
+ # @see https://github.com/socketry/nio4r/blob/master/lib/nio/selector.rb
905
+ #
906
+ def io_selector_backend(backend)
907
+ @options[:io_selector_backend] = backend.to_sym
908
+ end
909
+
910
+ def mutate_stdout_and_stderr_to_sync_on_write(enabled=true)
911
+ @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
912
+ end
515
913
  end
516
914
  end