puma 3.9.1 → 4.3.1

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 (82) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +232 -0
  3. data/README.md +162 -224
  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 +38 -0
  10. data/docs/restart.md +41 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +130 -37
  13. data/docs/tcp_mode.md +96 -0
  14. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  15. data/ext/puma_http11/extconf.rb +13 -0
  16. data/ext/puma_http11/http11_parser.c +115 -140
  17. data/ext/puma_http11/http11_parser.java.rl +21 -37
  18. data/ext/puma_http11/http11_parser.rl +9 -9
  19. data/ext/puma_http11/http11_parser_common.rl +3 -3
  20. data/ext/puma_http11/mini_ssl.c +104 -8
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +90 -108
  23. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  24. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
  25. data/ext/puma_http11/puma_http11.c +2 -0
  26. data/lib/puma.rb +16 -0
  27. data/lib/puma/accept_nonblock.rb +7 -1
  28. data/lib/puma/app/status.rb +40 -26
  29. data/lib/puma/binder.rb +57 -74
  30. data/lib/puma/cli.rb +26 -7
  31. data/lib/puma/client.rb +243 -190
  32. data/lib/puma/cluster.rb +78 -34
  33. data/lib/puma/commonlogger.rb +2 -0
  34. data/lib/puma/configuration.rb +24 -16
  35. data/lib/puma/const.rb +36 -18
  36. data/lib/puma/control_cli.rb +46 -19
  37. data/lib/puma/detect.rb +2 -0
  38. data/lib/puma/dsl.rb +329 -68
  39. data/lib/puma/events.rb +6 -1
  40. data/lib/puma/io_buffer.rb +3 -6
  41. data/lib/puma/jruby_restart.rb +2 -1
  42. data/lib/puma/launcher.rb +120 -58
  43. data/lib/puma/minissl.rb +69 -27
  44. data/lib/puma/minissl/context_builder.rb +76 -0
  45. data/lib/puma/null_io.rb +2 -0
  46. data/lib/puma/plugin.rb +7 -2
  47. data/lib/puma/plugin/tmp_restart.rb +2 -1
  48. data/lib/puma/rack/builder.rb +4 -1
  49. data/lib/puma/rack/urlmap.rb +2 -0
  50. data/lib/puma/rack_default.rb +2 -0
  51. data/lib/puma/reactor.rb +224 -34
  52. data/lib/puma/runner.rb +25 -4
  53. data/lib/puma/server.rb +148 -62
  54. data/lib/puma/single.rb +16 -5
  55. data/lib/puma/state_file.rb +2 -0
  56. data/lib/puma/tcp_logger.rb +2 -0
  57. data/lib/puma/thread_pool.rb +61 -38
  58. data/lib/puma/util.rb +2 -6
  59. data/lib/rack/handler/puma.rb +10 -4
  60. data/tools/docker/Dockerfile +16 -0
  61. data/tools/jungle/README.md +12 -2
  62. data/tools/jungle/init.d/README.md +2 -0
  63. data/tools/jungle/init.d/puma +8 -8
  64. data/tools/jungle/init.d/run-puma +1 -1
  65. data/tools/jungle/rc.d/README.md +74 -0
  66. data/tools/jungle/rc.d/puma +61 -0
  67. data/tools/jungle/rc.d/puma.conf +10 -0
  68. data/tools/trickletest.rb +1 -2
  69. metadata +29 -56
  70. data/.github/issue_template.md +0 -20
  71. data/Gemfile +0 -14
  72. data/Manifest.txt +0 -78
  73. data/Rakefile +0 -165
  74. data/Release.md +0 -9
  75. data/gemfiles/2.1-Gemfile +0 -12
  76. data/lib/puma/compat.rb +0 -14
  77. data/lib/puma/convenient.rb +0 -23
  78. data/lib/puma/daemon_ext.rb +0 -31
  79. data/lib/puma/delegation.rb +0 -11
  80. data/lib/puma/java_io_buffer.rb +0 -45
  81. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  82. data/puma.gemspec +0 -20
@@ -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,9 +7,18 @@ 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
- WORKER_CHECK_INTERVAL = 5
10
-
11
22
  def initialize(cli, events)
12
23
  super cli, events
13
24
 
@@ -24,7 +35,11 @@ module Puma
24
35
  @workers.each { |x| x.term }
25
36
 
26
37
  begin
27
- Process.waitall
38
+ loop do
39
+ wait_workers
40
+ break if @workers.empty?
41
+ sleep 0.2
42
+ end
28
43
  rescue Interrupt
29
44
  log "! Cancelled waiting for workers"
30
45
  end
@@ -56,12 +71,13 @@ module Puma
56
71
  @signal = "TERM"
57
72
  @options = options
58
73
  @first_term_sent = nil
74
+ @started_at = Time.now
59
75
  @last_checkin = Time.now
60
76
  @last_status = '{}'
61
- @dead = false
77
+ @term = false
62
78
  end
63
79
 
64
- attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
80
+ attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
65
81
 
66
82
  def booted?
67
83
  @stage == :booted
@@ -72,12 +88,8 @@ module Puma
72
88
  @stage = :booted
73
89
  end
74
90
 
75
- def dead?
76
- @dead
77
- end
78
-
79
- def dead!
80
- @dead = true
91
+ def term?
92
+ @term
81
93
  end
82
94
 
83
95
  def ping!(status)
@@ -94,9 +106,9 @@ module Puma
94
106
  if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
95
107
  @signal = "KILL"
96
108
  else
109
+ @term ||= true
97
110
  @first_term_sent ||= Time.now
98
111
  end
99
-
100
112
  Process.kill @signal, @pid
101
113
  rescue Errno::ESRCH
102
114
  end
@@ -170,7 +182,7 @@ module Puma
170
182
  def check_workers(force=false)
171
183
  return if !force && @next_check && @next_check >= Time.now
172
184
 
173
- @next_check = Time.now + WORKER_CHECK_INTERVAL
185
+ @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
174
186
 
175
187
  any = false
176
188
 
@@ -187,15 +199,7 @@ module Puma
187
199
  # during this loop by giving the kernel time to kill them.
188
200
  sleep 1 if any
189
201
 
190
- while @workers.any?
191
- pid = Process.waitpid(-1, Process::WNOHANG)
192
- break unless pid
193
-
194
- @workers.delete_if { |w| w.pid == pid }
195
- end
196
-
197
- @workers.delete_if(&:dead?)
198
-
202
+ wait_workers
199
203
  cull_workers
200
204
  spawn_workers
201
205
 
@@ -212,8 +216,10 @@ module Puma
212
216
  log "- Stopping #{w.pid} for phased upgrade..."
213
217
  end
214
218
 
215
- w.term
216
- log "- #{w.signal} sent to #{w.pid}..."
219
+ unless w.term?
220
+ w.term
221
+ log "- #{w.signal} sent to #{w.pid}..."
222
+ end
217
223
  end
218
224
  end
219
225
  end
@@ -224,12 +230,13 @@ module Puma
224
230
  begin
225
231
  @wakeup.write "!" unless @wakeup.closed?
226
232
  rescue SystemCallError, IOError
233
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
227
234
  end
228
235
  end
229
236
 
230
237
  def worker(index, master)
231
- title = "puma: cluster worker #{index}: #{master}"
232
- title << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
238
+ title = "puma: cluster worker #{index}: #{master}"
239
+ title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
233
240
  $0 = title
234
241
 
235
242
  Signal.trap "SIGINT", "IGNORE"
@@ -239,6 +246,7 @@ module Puma
239
246
  @suicide_pipe.close
240
247
 
241
248
  Thread.new do
249
+ Puma.set_thread_name "worker check pipe"
242
250
  IO.select [@check_pipe]
243
251
  log "! Detected parent died, dying"
244
252
  exit! 1
@@ -261,27 +269,33 @@ module Puma
261
269
  server = start_server
262
270
 
263
271
  Signal.trap "SIGTERM" do
272
+ @worker_write << "e#{Process.pid}\n" rescue nil
264
273
  server.stop
265
274
  end
266
275
 
267
276
  begin
268
277
  @worker_write << "b#{Process.pid}\n"
269
278
  rescue SystemCallError, IOError
279
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
270
280
  STDERR.puts "Master seems to have exited, exiting."
271
281
  return
272
282
  end
273
283
 
274
284
  Thread.new(@worker_write) do |io|
285
+ Puma.set_thread_name "stat payload"
275
286
  base_payload = "p#{Process.pid}"
276
287
 
277
288
  while true
278
- sleep WORKER_CHECK_INTERVAL
289
+ sleep Const::WORKER_CHECK_INTERVAL
279
290
  begin
280
- b = server.backlog
281
- r = server.running
282
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
291
+ b = server.backlog || 0
292
+ r = server.running || 0
293
+ t = server.pool_capacity || 0
294
+ m = server.max_threads || 0
295
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
283
296
  io << payload
284
297
  rescue IOError
298
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
285
299
  break
286
300
  end
287
301
  end
@@ -334,11 +348,13 @@ module Puma
334
348
  Dir.chdir dir
335
349
  end
336
350
 
351
+ # Inside of a child process, this will return all zeroes, as @workers is only populated in
352
+ # the master process.
337
353
  def stats
338
354
  old_worker_count = @workers.count { |w| w.phase != @phase }
339
355
  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(",") + ']'
341
- %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
356
+ worker_status = '[' + @workers.map { |w| %Q!{ "started_at": "#{w.started_at.utc.iso8601}", "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(",") + ']'
357
+ %Q!{ "started_at": "#{@started_at.utc.iso8601}", "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
342
358
  end
343
359
 
344
360
  def preload?
@@ -372,7 +388,13 @@ module Puma
372
388
  log "Early termination of worker"
373
389
  exit! 0
374
390
  else
391
+ @launcher.close_binder_listeners
392
+
393
+ stop_workers
375
394
  stop
395
+
396
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
397
+ exit 0 # Clean exit, workers were stopped
376
398
  end
377
399
  end
378
400
  end
@@ -466,7 +488,7 @@ module Puma
466
488
 
467
489
  force_check = false
468
490
 
469
- res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
491
+ res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
470
492
 
471
493
  if res
472
494
  req = read.read_nonblock(1)
@@ -482,8 +504,11 @@ module Puma
482
504
  w.boot!
483
505
  log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
484
506
  force_check = true
507
+ when "e"
508
+ # external term, see worker method, Signal.trap "SIGTERM"
509
+ w.instance_variable_set :@term, true
485
510
  when "t"
486
- w.dead!
511
+ w.term unless w.term?
487
512
  force_check = true
488
513
  when "p"
489
514
  w.ping!(result.sub(/^\d+/,'').chomp)
@@ -506,5 +531,24 @@ module Puma
506
531
  @wakeup.close
507
532
  end
508
533
  end
534
+
535
+ private
536
+
537
+ # loops thru @workers, removing workers that exited, and calling
538
+ # `#term` if needed
539
+ def wait_workers
540
+ @workers.reject! do |w|
541
+ begin
542
+ if Process.wait(w.pid, Process::WNOHANG)
543
+ true
544
+ else
545
+ w.term if w.term?
546
+ nil
547
+ end
548
+ rescue Errno::ECHILD
549
+ true # child is already terminated
550
+ end
551
+ end
552
+ end
509
553
  end
510
554
  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'
@@ -18,7 +20,7 @@ module Puma
18
20
  # In this class any "user" specified options take precedence over any
19
21
  # "file" specified options, take precedence over any "default" options.
20
22
  #
21
- # User input is prefered over "defaults":
23
+ # User input is preferred over "defaults":
22
24
  # user_options = { foo: "bar" }
23
25
  # default_options = { foo: "zoo" }
24
26
  # options = UserFileDefaultOptions.new(user_options, default_options)
@@ -30,7 +32,7 @@ module Puma
30
32
  # puts options.all_of(:foo)
31
33
  # # => ["bar", "zoo"]
32
34
  #
33
- # A "file" option can be set. This config will be prefered over "default" options
35
+ # A "file" option can be set. This config will be preferred over "default" options
34
36
  # but will defer to any available "user" specified options.
35
37
  #
36
38
  # user_options = { foo: "bar" }
@@ -180,30 +182,32 @@ 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,
190
+ :raise_exception_on_sigterm => true
187
191
  }
188
192
  end
189
193
 
190
194
  def load
195
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
196
+
197
+ @options
198
+ end
199
+
200
+ def config_files
191
201
  files = @options.all_of(:config_files)
192
202
 
193
- if files.empty?
194
- imp = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find { |f|
195
- File.exist?(f)
196
- }
203
+ return [] if files == ['-']
204
+ return files if files.any?
197
205
 
198
- files << imp
199
- elsif files == ["-"]
200
- files = []
206
+ first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
207
+ File.exist?(f)
201
208
  end
202
209
 
203
- files.each do |f|
204
- @file_dsl._load_from(f)
205
- end
206
- @options
210
+ [first_default_file]
207
211
  end
208
212
 
209
213
  # Call once all configuration (included from rackup files)
@@ -263,6 +267,10 @@ module Puma
263
267
  @options[:environment]
264
268
  end
265
269
 
270
+ def environment_str
271
+ environment.respond_to?(:call) ? environment.call : environment
272
+ end
273
+
266
274
  def load_plugin(name)
267
275
  @plugins.create name
268
276
  end
@@ -340,7 +348,7 @@ module Puma
340
348
  end
341
349
 
342
350
  if bytes
343
- token = ""
351
+ token = "".dup
344
352
  bytes.each_byte { |b| token << b.to_s(16) }
345
353
  else
346
354
  token = (0..count).to_a.map { rand(255).to_s(16) }.join
@@ -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.9.1".freeze
99
- CODE_NAME = "Private Caller".freeze
103
+ PUMA_VERSION = VERSION = "4.3.1".freeze
104
+ CODE_NAME = "Mysterious Traveller".freeze
100
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
101
106
 
102
107
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -113,31 +118,35 @@ 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
119
131
  QUERY_STRING = 'QUERY_STRING'.freeze
132
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
120
133
 
121
134
  PATH_INFO = 'PATH_INFO'.freeze
122
135
 
123
136
  PUMA_TMP_BASE = "puma".freeze
124
137
 
125
- # Indicate that we couldn't parse the request
126
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
127
-
128
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
129
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
130
-
131
- # The standard empty 408 response for requests that timed out.
132
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
133
-
134
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
135
-
136
- # Indicate that there was an internal error, obviously.
137
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
138
-
139
- # A common header for indicating the server is too busy. Not used yet.
140
- ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
138
+ ERROR_RESPONSE = {
139
+ # Indicate that we couldn't parse the request
140
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
141
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
142
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
143
+ # The standard empty 408 response for requests that timed out.
144
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
145
+ # Indicate that there was an internal error, obviously.
146
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
147
+ # A common header for indicating the server is too busy. Not used yet.
148
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
149
+ }
141
150
 
142
151
  # The basic max request size we'll try to read.
143
152
  CHUNK_SIZE = 16 * 1024
@@ -155,6 +164,9 @@ module Puma
155
164
  LINE_END = "\r\n".freeze
156
165
  REMOTE_ADDR = "REMOTE_ADDR".freeze
157
166
  HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
167
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
168
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
169
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
158
170
 
159
171
  SERVER_NAME = "SERVER_NAME".freeze
160
172
  SERVER_PORT = "SERVER_PORT".freeze
@@ -220,5 +232,11 @@ module Puma
220
232
  HIJACK_P = "rack.hijack?".freeze
221
233
  HIJACK = "rack.hijack".freeze
222
234
  HIJACK_IO = "rack.hijack_io".freeze
235
+
236
+ EARLY_HINTS = "rack.early_hints".freeze
237
+
238
+ # Mininum interval to checks worker health
239
+ WORKER_CHECK_INTERVAL = 5
240
+
223
241
  end
224
242
  end