puma 3.8.2 → 3.12.6

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 (67) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +153 -0
  3. data/README.md +140 -230
  4. data/docs/architecture.md +36 -0
  5. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  6. data/docs/images/puma-connection-flow.png +0 -0
  7. data/docs/images/puma-general-arch.png +0 -0
  8. data/docs/plugins.md +28 -0
  9. data/docs/restart.md +39 -0
  10. data/docs/signals.md +56 -3
  11. data/docs/systemd.md +112 -37
  12. data/ext/puma_http11/http11_parser.c +87 -85
  13. data/ext/puma_http11/http11_parser.rl +12 -10
  14. data/ext/puma_http11/mini_ssl.c +31 -5
  15. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  16. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
  17. data/lib/puma/app/status.rb +8 -0
  18. data/lib/puma/binder.rb +22 -17
  19. data/lib/puma/cli.rb +22 -7
  20. data/lib/puma/client.rb +41 -2
  21. data/lib/puma/cluster.rb +28 -7
  22. data/lib/puma/commonlogger.rb +2 -0
  23. data/lib/puma/configuration.rb +21 -14
  24. data/lib/puma/const.rb +17 -2
  25. data/lib/puma/control_cli.rb +16 -14
  26. data/lib/puma/convenient.rb +2 -0
  27. data/lib/puma/daemon_ext.rb +2 -0
  28. data/lib/puma/delegation.rb +2 -0
  29. data/lib/puma/detect.rb +2 -0
  30. data/lib/puma/dsl.rb +46 -9
  31. data/lib/puma/events.rb +3 -2
  32. data/lib/puma/io_buffer.rb +2 -0
  33. data/lib/puma/java_io_buffer.rb +2 -0
  34. data/lib/puma/jruby_restart.rb +2 -1
  35. data/lib/puma/launcher.rb +42 -20
  36. data/lib/puma/minissl.rb +67 -28
  37. data/lib/puma/null_io.rb +2 -0
  38. data/lib/puma/plugin/tmp_restart.rb +0 -1
  39. data/lib/puma/plugin.rb +2 -0
  40. data/lib/puma/rack/builder.rb +2 -1
  41. data/lib/puma/reactor.rb +137 -0
  42. data/lib/puma/runner.rb +16 -3
  43. data/lib/puma/server.rb +145 -29
  44. data/lib/puma/single.rb +14 -3
  45. data/lib/puma/state_file.rb +2 -0
  46. data/lib/puma/tcp_logger.rb +2 -0
  47. data/lib/puma/thread_pool.rb +55 -6
  48. data/lib/puma/util.rb +1 -0
  49. data/lib/puma.rb +8 -0
  50. data/lib/rack/handler/puma.rb +13 -2
  51. data/tools/jungle/README.md +12 -2
  52. data/tools/jungle/init.d/README.md +2 -0
  53. data/tools/jungle/init.d/puma +2 -2
  54. data/tools/jungle/init.d/run-puma +1 -1
  55. data/tools/jungle/rc.d/README.md +74 -0
  56. data/tools/jungle/rc.d/puma +61 -0
  57. data/tools/jungle/rc.d/puma.conf +10 -0
  58. data/tools/trickletest.rb +1 -1
  59. metadata +21 -95
  60. data/.github/issue_template.md +0 -20
  61. data/Gemfile +0 -12
  62. data/Manifest.txt +0 -78
  63. data/Rakefile +0 -158
  64. data/Release.md +0 -9
  65. data/gemfiles/2.1-Gemfile +0 -12
  66. data/puma.gemspec +0 -52
  67. /data/{DEPLOYMENT.md → docs/deployment.md} +0 -0
@@ -182,9 +182,6 @@ static final int puma_parser_start = 1;
182
182
  static final int puma_parser_first_final = 47;
183
183
  static final int puma_parser_error = 0;
184
184
 
185
- static final int puma_parser_en_main = 1;
186
-
187
-
188
185
  // line 69 "ext/puma_http11/http11_parser.java.rl"
189
186
 
190
187
  public static interface ElementCB {
@@ -220,7 +217,7 @@ static final int puma_parser_en_main = 1;
220
217
  public void init() {
221
218
  cs = 0;
222
219
 
223
-
220
+
224
221
  // line 225 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
225
222
  {
226
223
  cs = puma_parser_start;
@@ -252,7 +249,7 @@ static final int puma_parser_en_main = 1;
252
249
  byte[] data = buffer.bytes();
253
250
  parser.buffer = buffer;
254
251
 
255
-
252
+
256
253
  // line 257 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
257
254
  {
258
255
  int _klen;
@@ -347,7 +344,7 @@ case 1:
347
344
  break;
348
345
  case 3:
349
346
  // line 17 "ext/puma_http11/http11_parser.java.rl"
350
- {
347
+ {
351
348
  parser.field_len = p-parser.field_start;
352
349
  }
353
350
  break;
@@ -357,7 +354,7 @@ case 1:
357
354
  break;
358
355
  case 5:
359
356
  // line 22 "ext/puma_http11/http11_parser.java.rl"
360
- {
357
+ {
361
358
  if(parser.http_field != null) {
362
359
  parser.http_field.call(parser.data, parser.field_start, parser.field_len, parser.mark, p-parser.mark);
363
360
  }
@@ -365,21 +362,21 @@ case 1:
365
362
  break;
366
363
  case 6:
367
364
  // line 27 "ext/puma_http11/http11_parser.java.rl"
368
- {
369
- if(parser.request_method != null)
365
+ {
366
+ if(parser.request_method != null)
370
367
  parser.request_method.call(parser.data, parser.mark, p-parser.mark);
371
368
  }
372
369
  break;
373
370
  case 7:
374
371
  // line 31 "ext/puma_http11/http11_parser.java.rl"
375
- {
372
+ {
376
373
  if(parser.request_uri != null)
377
374
  parser.request_uri.call(parser.data, parser.mark, p-parser.mark);
378
375
  }
379
376
  break;
380
377
  case 8:
381
378
  // line 35 "ext/puma_http11/http11_parser.java.rl"
382
- {
379
+ {
383
380
  if(parser.fragment != null)
384
381
  parser.fragment.call(parser.data, parser.mark, p-parser.mark);
385
382
  }
@@ -390,14 +387,14 @@ case 1:
390
387
  break;
391
388
  case 10:
392
389
  // line 41 "ext/puma_http11/http11_parser.java.rl"
393
- {
390
+ {
394
391
  if(parser.query_string != null)
395
392
  parser.query_string.call(parser.data, parser.query_start, p-parser.query_start);
396
393
  }
397
394
  break;
398
395
  case 11:
399
396
  // line 46 "ext/puma_http11/http11_parser.java.rl"
400
- {
397
+ {
401
398
  if(parser.http_version != null)
402
399
  parser.http_version.call(parser.data, parser.mark, p-parser.mark);
403
400
  }
@@ -411,8 +408,8 @@ case 1:
411
408
  break;
412
409
  case 13:
413
410
  // line 56 "ext/puma_http11/http11_parser.java.rl"
414
- {
415
- parser.body_start = p + 1;
411
+ {
412
+ parser.body_start = p + 1;
416
413
  if(parser.header_done != null)
417
414
  parser.header_done.call(parser.data, p + 1, pe - p - 1);
418
415
  { p += 1; _goto_targ = 5; if (true) continue _goto;}
@@ -442,7 +439,7 @@ case 5:
442
439
 
443
440
  parser.cs = cs;
444
441
  parser.nread += (p - off);
445
-
442
+
446
443
  assert p <= pe : "buffer overflow after parsing execute";
447
444
  assert parser.nread <= len : "nread longer than length";
448
445
  assert parser.body_start <= len : "body starts after buffer end";
@@ -6,6 +6,7 @@ import org.jruby.RubyModule;
6
6
  import org.jruby.RubyObject;
7
7
  import org.jruby.RubyString;
8
8
  import org.jruby.anno.JRubyMethod;
9
+ import org.jruby.javasupport.JavaEmbedUtils;
9
10
  import org.jruby.runtime.Block;
10
11
  import org.jruby.runtime.ObjectAllocator;
11
12
  import org.jruby.runtime.ThreadContext;
@@ -18,6 +19,7 @@ import javax.net.ssl.SSLContext;
18
19
  import javax.net.ssl.SSLEngine;
19
20
  import javax.net.ssl.SSLEngineResult;
20
21
  import javax.net.ssl.SSLException;
22
+ import javax.net.ssl.SSLPeerUnverifiedException;
21
23
  import javax.net.ssl.SSLSession;
22
24
  import java.io.FileInputStream;
23
25
  import java.io.IOException;
@@ -27,6 +29,7 @@ import java.security.KeyStore;
27
29
  import java.security.KeyStoreException;
28
30
  import java.security.NoSuchAlgorithmException;
29
31
  import java.security.UnrecoverableKeyException;
32
+ import java.security.cert.CertificateEncodingException;
30
33
  import java.security.cert.CertificateException;
31
34
 
32
35
  import static javax.net.ssl.SSLEngineResult.Status;
@@ -167,6 +170,12 @@ public class MiniSSL extends RubyObject {
167
170
  engine.setNeedClientAuth(true);
168
171
  }
169
172
 
173
+ IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
174
+ if (!sslCipherListObject.isNil()) {
175
+ String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
176
+ engine.setEnabledCipherSuites(sslCipherList);
177
+ }
178
+
170
179
  SSLSession session = engine.getSession();
171
180
  inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
172
181
  outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
@@ -333,7 +342,11 @@ public class MiniSSL extends RubyObject {
333
342
  }
334
343
 
335
344
  @JRubyMethod
336
- public IRubyObject peercert() {
337
- return getRuntime().getNil();
345
+ public IRubyObject peercert() throws CertificateEncodingException {
346
+ try {
347
+ return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
348
+ } catch (SSLPeerUnverifiedException ex) {
349
+ return getRuntime().getNil();
350
+ }
338
351
  }
339
352
  }
@@ -55,6 +55,14 @@ module Puma
55
55
  return rack_response(200, OK_STATUS)
56
56
  end
57
57
 
58
+ when /\/gc$/
59
+ GC.start
60
+ return rack_response(200, OK_STATUS)
61
+
62
+ when /\/gc-stats$/
63
+ json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
64
+ return rack_response(200, json)
65
+
58
66
  when /\/stats$/
59
67
  return rack_response(200, @cli.stats)
60
68
  else
data/lib/puma/binder.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'socket'
3
5
 
@@ -90,19 +92,19 @@ module Puma
90
92
  case uri.scheme
91
93
  when "tcp"
92
94
  if fd = @inherited_fds.delete(str)
93
- logger.log "* Inherited #{str}"
94
95
  io = inherit_tcp_listener uri.host, uri.port, fd
96
+ logger.log "* Inherited #{str}"
95
97
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
96
- logger.log "* Activated #{str}"
97
98
  io = inherit_tcp_listener uri.host, uri.port, sock
99
+ logger.log "* Activated #{str}"
98
100
  else
99
101
  params = Util.parse_query uri.query
100
102
 
101
103
  opt = params.key?('low_latency')
102
104
  bak = params.fetch('backlog', 1024).to_i
103
105
 
104
- logger.log "* Listening on #{str}"
105
106
  io = add_tcp_listener uri.host, uri.port, opt, bak
107
+ logger.log "* Listening on #{str}"
106
108
  end
107
109
 
108
110
  @listeners << [str, io] if io
@@ -110,17 +112,15 @@ module Puma
110
112
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
111
113
 
112
114
  if fd = @inherited_fds.delete(str)
113
- logger.log "* Inherited #{str}"
114
115
  io = inherit_unix_listener path, fd
116
+ logger.log "* Inherited #{str}"
115
117
  elsif sock = @activated_sockets.delete([ :unix, path ])
116
- logger.log "* Activated #{str}"
117
118
  io = inherit_unix_listener path, sock
119
+ logger.log "* Activated #{str}"
118
120
  else
119
- logger.log "* Listening on #{str}"
120
-
121
121
  umask = nil
122
122
  mode = nil
123
- backlog = nil
123
+ backlog = 1024
124
124
 
125
125
  if uri.query
126
126
  params = Util.parse_query uri.query
@@ -139,6 +139,7 @@ module Puma
139
139
  end
140
140
 
141
141
  io = add_unix_listener path, umask, mode, backlog
142
+ logger.log "* Listening on #{str}"
142
143
  end
143
144
 
144
145
  @listeners << [str, io]
@@ -162,6 +163,7 @@ module Puma
162
163
  end
163
164
 
164
165
  ctx.keystore_pass = params['keystore-pass']
166
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
165
167
  else
166
168
  unless params['key']
167
169
  @events.error "Please specify the SSL key via 'key='"
@@ -182,6 +184,7 @@ module Puma
182
184
  end
183
185
 
184
186
  ctx.ca = params['ca'] if params['ca']
187
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
185
188
  end
186
189
 
187
190
  if params['verify_mode']
@@ -202,11 +205,11 @@ module Puma
202
205
  logger.log "* Inherited #{str}"
203
206
  io = inherit_ssl_listener fd, ctx
204
207
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
205
- logger.log "* Activated #{str}"
206
208
  io = inherit_ssl_listener sock, ctx
209
+ logger.log "* Activated #{str}"
207
210
  else
208
- logger.log "* Listening on #{str}"
209
211
  io = add_ssl_listener uri.host, uri.port, ctx
212
+ logger.log "* Listening on #{str}"
210
213
  end
211
214
 
212
215
  @listeners << [str, io] if io
@@ -245,9 +248,10 @@ module Puma
245
248
  end
246
249
  end
247
250
 
248
- def localhost_addresses
249
- addrs = TCPSocket.gethostbyname "localhost"
250
- addrs[3..-1].uniq
251
+ def loopback_addresses
252
+ Socket.ip_address_list.select do |addrinfo|
253
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
254
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
251
255
  end
252
256
 
253
257
  # Tell the server to listen on host +host+, port +port+.
@@ -259,7 +263,7 @@ module Puma
259
263
  #
260
264
  def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
261
265
  if host == "localhost"
262
- localhost_addresses.each do |addr|
266
+ loopback_addresses.each do |addr|
263
267
  add_tcp_listener addr, port, optimize_for_latency, backlog
264
268
  end
265
269
  return
@@ -298,7 +302,7 @@ module Puma
298
302
  MiniSSL.check
299
303
 
300
304
  if host == "localhost"
301
- localhost_addresses.each do |addr|
305
+ loopback_addresses.each do |addr|
302
306
  add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
303
307
  end
304
308
  return
@@ -312,6 +316,7 @@ module Puma
312
316
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
313
317
  s.listen backlog
314
318
 
319
+
315
320
  ssl = MiniSSL::Server.new s, ctx
316
321
  env = @proto_env.dup
317
322
  env[HTTPS_KEY] = HTTPS
@@ -343,7 +348,7 @@ module Puma
343
348
 
344
349
  # Tell the server to listen on +path+ as a UNIX domain socket.
345
350
  #
346
- def add_unix_listener(path, umask=nil, mode=nil, backlog=nil)
351
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
347
352
  @unix_paths << path
348
353
 
349
354
  # Let anyone connect by default
@@ -364,7 +369,7 @@ module Puma
364
369
  end
365
370
 
366
371
  s = UNIXServer.new(path)
367
- s.listen backlog if backlog
372
+ s.listen backlog
368
373
  @ios << s
369
374
  ensure
370
375
  File.umask old_mask
data/lib/puma/cli.rb CHANGED
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'uri'
3
5
 
6
+ require 'puma'
4
7
  require 'puma/configuration'
5
8
  require 'puma/launcher'
6
9
  require 'puma/const'
@@ -83,6 +86,14 @@ module Puma
83
86
  raise UnsupportedOption
84
87
  end
85
88
 
89
+ def configure_control_url(command_line_arg)
90
+ if command_line_arg
91
+ @control_url = command_line_arg
92
+ elsif Puma.jruby?
93
+ unsupported "No default url available on JRuby"
94
+ end
95
+ end
96
+
86
97
  # Build the OptionParser object to handle the available options.
87
98
  #
88
99
 
@@ -97,13 +108,13 @@ module Puma
97
108
  file_config.load arg
98
109
  end
99
110
 
100
- o.on "--control URL", "The bind url to use for the control server",
101
- "Use 'auto' to use temp unix server" do |arg|
102
- if arg
103
- @control_url = arg
104
- elsif Puma.jruby?
105
- unsupported "No default url available on JRuby"
106
- end
111
+ o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
112
+ configure_control_url(arg)
113
+ end
114
+
115
+ # alias --control-url for backwards-compatibility
116
+ o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
117
+ configure_control_url(arg)
107
118
  end
108
119
 
109
120
  o.on "--control-token TOKEN",
@@ -181,6 +192,10 @@ module Puma
181
192
  user_config.tcp_mode!
182
193
  end
183
194
 
195
+ o.on "--early-hints", "Enable early hints support" do
196
+ user_config.early_hints
197
+ end
198
+
184
199
  o.on "-V", "--version", "Print the version information" do
185
200
  puts "puma version #{Puma::Const::VERSION}"
186
201
  exit 0
data/lib/puma/client.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class IO
2
4
  # We need to use this for a jruby work around on both 1.8 and 1.9.
3
5
  # So this either creates the constant (on 1.8), or harmlessly
@@ -21,6 +23,17 @@ module Puma
21
23
 
22
24
  class ConnectionError < RuntimeError; end
23
25
 
26
+ # An instance of this class represents a unique request from a client.
27
+ # For example a web request from a browser or from CURL. This
28
+ #
29
+ # An instance of `Puma::Client` can be used as if it were an IO object
30
+ # for example it is passed into `IO.select` inside of the `Puma::Reactor`.
31
+ # This is accomplished by the `to_io` method which gets called on any
32
+ # non-IO objects being used with the IO api such as `IO.select.
33
+ #
34
+ # Instances of this class are responsible for knowing if
35
+ # the header and body are fully buffered via the `try_to_finish` method.
36
+ # They can be used to "time out" a response via the `timeout_at` reader.
24
37
  class Client
25
38
  include Puma::Const
26
39
  extend Puma::Delegation
@@ -111,6 +124,7 @@ module Puma
111
124
  begin
112
125
  @io.close
113
126
  rescue IOError
127
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
114
128
  end
115
129
  end
116
130
 
@@ -156,6 +170,7 @@ module Puma
156
170
  if len == 0
157
171
  @body.rewind
158
172
  rest = io.read
173
+ rest = rest[2..-1] if rest.start_with?("\r\n")
159
174
  @buffer = rest.empty? ? nil : rest
160
175
  @requests_served += 1
161
176
  @ready = true
@@ -229,8 +244,16 @@ module Puma
229
244
 
230
245
  te = @env[TRANSFER_ENCODING2]
231
246
 
232
- if te && CHUNKED.casecmp(te) == 0
233
- return setup_chunked_body(body)
247
+ if te
248
+ if te.include?(",")
249
+ te.split(",").each do |part|
250
+ if CHUNKED.casecmp(part.strip) == 0
251
+ return setup_chunked_body(body)
252
+ end
253
+ end
254
+ elsif CHUNKED.casecmp(te) == 0
255
+ return setup_chunked_body(body)
256
+ end
234
257
  end
235
258
 
236
259
  @chunked_body = false
@@ -283,6 +306,14 @@ module Puma
283
306
  raise ConnectionError, "Connection error detected during read"
284
307
  end
285
308
 
309
+ # No data means a closed socket
310
+ unless data
311
+ @buffer = nil
312
+ @requests_served += 1
313
+ @ready = true
314
+ raise EOFError
315
+ end
316
+
286
317
  if @buffer
287
318
  @buffer << data
288
319
  else
@@ -312,6 +343,14 @@ module Puma
312
343
  raise e
313
344
  end
314
345
 
346
+ # No data means a closed socket
347
+ unless data
348
+ @buffer = nil
349
+ @requests_served += 1
350
+ @ready = true
351
+ raise EOFError
352
+ end
353
+
315
354
  if @buffer
316
355
  @buffer << data
317
356
  else
data/lib/puma/cluster.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/runner'
2
4
  require 'puma/util'
3
5
  require 'puma/plugin'
@@ -5,6 +7,17 @@ require 'puma/plugin'
5
7
  require 'time'
6
8
 
7
9
  module Puma
10
+ # This class is instantiated by the `Puma::Launcher` and used
11
+ # to boot and serve a Ruby application when puma "workers" are needed
12
+ # i.e. when using multi-processes. For example `$ puma -w 5`
13
+ #
14
+ # At the core of this class is running an instance of `Puma::Server` which
15
+ # gets created via the `start_server` method from the `Puma::Runner` class
16
+ # that this inherits from.
17
+ #
18
+ # An instance of this class will spawn the number of processes passed in
19
+ # via the `spawn_workers` method call. Each worker will have it's own
20
+ # instance of a `Puma::Server`.
8
21
  class Cluster < Runner
9
22
  WORKER_CHECK_INTERVAL = 5
10
23
 
@@ -24,7 +37,7 @@ module Puma
24
37
  @workers.each { |x| x.term }
25
38
 
26
39
  begin
27
- Process.waitall
40
+ @workers.each { |w| Process.waitpid(w.pid) }
28
41
  rescue Interrupt
29
42
  log "! Cancelled waiting for workers"
30
43
  end
@@ -224,12 +237,13 @@ module Puma
224
237
  begin
225
238
  @wakeup.write "!" unless @wakeup.closed?
226
239
  rescue SystemCallError, IOError
240
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
227
241
  end
228
242
  end
229
243
 
230
244
  def worker(index, master)
231
- title = "puma: cluster worker #{index}: #{master}"
232
- title << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
245
+ title = "puma: cluster worker #{index}: #{master}"
246
+ title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
233
247
  $0 = title
234
248
 
235
249
  Signal.trap "SIGINT", "IGNORE"
@@ -267,6 +281,7 @@ module Puma
267
281
  begin
268
282
  @worker_write << "b#{Process.pid}\n"
269
283
  rescue SystemCallError, IOError
284
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
270
285
  STDERR.puts "Master seems to have exited, exiting."
271
286
  return
272
287
  end
@@ -277,11 +292,14 @@ module Puma
277
292
  while true
278
293
  sleep WORKER_CHECK_INTERVAL
279
294
  begin
280
- b = server.backlog
281
- r = server.running
282
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
295
+ b = server.backlog || 0
296
+ r = server.running || 0
297
+ t = server.pool_capacity || 0
298
+ m = server.max_threads || 0
299
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
283
300
  io << payload
284
301
  rescue IOError
302
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
285
303
  break
286
304
  end
287
305
  end
@@ -337,7 +355,7 @@ module Puma
337
355
  def stats
338
356
  old_worker_count = @workers.count { |w| w.phase != @phase }
339
357
  booted_worker_count = @workers.count { |w| w.booted? }
340
- worker_status = '[' + @workers.map{ |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
358
+ worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
341
359
  %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
342
360
  end
343
361
 
@@ -372,7 +390,10 @@ module Puma
372
390
  log "Early termination of worker"
373
391
  exit! 0
374
392
  else
393
+ stop_workers
375
394
  stop
395
+
396
+ raise SignalException, "SIGTERM"
376
397
  end
377
398
  end
378
399
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Rack::CommonLogger forwards every request to the given +app+, and
3
5
  # logs a line in the
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/rack/builder'
2
4
  require 'puma/plugin'
3
5
  require 'puma/const'
@@ -180,30 +182,31 @@ module Puma
180
182
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
181
183
  :remote_address => :socket,
182
184
  :tag => method(:infer_tag),
183
- :environment => ->{ ENV['RACK_ENV'] || "development" },
185
+ :environment => -> { ENV['RACK_ENV'] || "development" },
184
186
  :rackup => DefaultRackup,
185
187
  :logger => STDOUT,
186
- :persistent_timeout => Const::PERSISTENT_TIMEOUT
188
+ :persistent_timeout => Const::PERSISTENT_TIMEOUT,
189
+ :first_data_timeout => Const::FIRST_DATA_TIMEOUT
187
190
  }
188
191
  end
189
192
 
190
193
  def load
194
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
195
+
196
+ @options
197
+ end
198
+
199
+ def config_files
191
200
  files = @options.all_of(:config_files)
192
201
 
193
- if files.empty?
194
- imp = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find { |f|
195
- File.exist?(f)
196
- }
202
+ return [] if files == ['-']
203
+ return files if files.any?
197
204
 
198
- files << imp
199
- elsif files == ["-"]
200
- files = []
205
+ first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
206
+ File.exist?(f)
201
207
  end
202
208
 
203
- files.each do |f|
204
- @file_dsl._load_from(f)
205
- end
206
- @options
209
+ [first_default_file]
207
210
  end
208
211
 
209
212
  # Call once all configuration (included from rackup files)
@@ -263,6 +266,10 @@ module Puma
263
266
  @options[:environment]
264
267
  end
265
268
 
269
+ def environment_str
270
+ environment.respond_to?(:call) ? environment.call : environment
271
+ end
272
+
266
273
  def load_plugin(name)
267
274
  @plugins.create name
268
275
  end
@@ -340,7 +347,7 @@ module Puma
340
347
  end
341
348
 
342
349
  if bytes
343
- token = ""
350
+ token = "".dup
344
351
  bytes.each_byte { |b| token << b.to_s(16) }
345
352
  else
346
353
  token = (0..count).to_a.map { rand(255).to_s(16) }.join
data/lib/puma/const.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  #encoding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  module Puma
3
5
  class UnsupportedOption < RuntimeError
4
6
  end
@@ -53,6 +55,8 @@ module Puma
53
55
  415 => 'Unsupported Media Type',
54
56
  416 => 'Range Not Satisfiable',
55
57
  417 => 'Expectation Failed',
58
+ 418 => 'I\'m A Teapot',
59
+ 421 => 'Misdirected Request',
56
60
  422 => 'Unprocessable Entity',
57
61
  423 => 'Locked',
58
62
  424 => 'Failed Dependency',
@@ -60,6 +64,7 @@ module Puma
60
64
  428 => 'Precondition Required',
61
65
  429 => 'Too Many Requests',
62
66
  431 => 'Request Header Fields Too Large',
67
+ 451 => 'Unavailable For Legal Reasons',
63
68
  500 => 'Internal Server Error',
64
69
  501 => 'Not Implemented',
65
70
  502 => 'Bad Gateway',
@@ -95,8 +100,8 @@ module Puma
95
100
  # too taxing on performance.
96
101
  module Const
97
102
 
98
- PUMA_VERSION = VERSION = "3.8.2".freeze
99
- CODE_NAME = "Sassy Salamander".freeze
103
+ PUMA_VERSION = VERSION = "3.12.6".freeze
104
+ CODE_NAME = "Llamas in Pajamas".freeze
100
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
101
106
 
102
107
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -113,6 +118,13 @@ module Puma
113
118
  # sending data back
114
119
  WRITE_TIMEOUT = 10
115
120
 
121
+ # How many requests to attempt inline before sending a client back to
122
+ # the reactor to be subject to normal ordering. The idea here is that
123
+ # we amortize the cost of going back to the reactor for a well behaved
124
+ # but very "greedy" client across 10 requests. This prevents a not
125
+ # well behaved client from monopolizing the thread forever.
126
+ MAX_FAST_INLINE = 10
127
+
116
128
  # The original URI requested by the client.
117
129
  REQUEST_URI= 'REQUEST_URI'.freeze
118
130
  REQUEST_PATH = 'REQUEST_PATH'.freeze
@@ -216,9 +228,12 @@ module Puma
216
228
  COLON = ": ".freeze
217
229
 
218
230
  NEWLINE = "\n".freeze
231
+ HTTP_INJECTION_REGEX = /[\r\n]/.freeze
219
232
 
220
233
  HIJACK_P = "rack.hijack?".freeze
221
234
  HIJACK = "rack.hijack".freeze
222
235
  HIJACK_IO = "rack.hijack_io".freeze
236
+
237
+ EARLY_HINTS = "rack.early_hints".freeze
223
238
  end
224
239
  end