puma 3.6.0 → 3.12.0

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

Potentially problematic release.


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

Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +293 -79
  3. data/README.md +143 -227
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +0 -0
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +28 -0
  10. data/docs/restart.md +39 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +124 -22
  13. data/ext/puma_http11/extconf.rb +2 -0
  14. data/ext/puma_http11/http11_parser.c +85 -84
  15. data/ext/puma_http11/http11_parser.h +1 -0
  16. data/ext/puma_http11/http11_parser.rl +10 -9
  17. data/ext/puma_http11/io_buffer.c +7 -7
  18. data/ext/puma_http11/mini_ssl.c +62 -6
  19. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
  21. data/ext/puma_http11/puma_http11.c +1 -0
  22. data/lib/puma.rb +13 -5
  23. data/lib/puma/app/status.rb +8 -0
  24. data/lib/puma/binder.rb +21 -14
  25. data/lib/puma/cli.rb +49 -33
  26. data/lib/puma/client.rb +39 -4
  27. data/lib/puma/cluster.rb +51 -11
  28. data/lib/puma/commonlogger.rb +19 -20
  29. data/lib/puma/compat.rb +3 -7
  30. data/lib/puma/configuration.rb +133 -130
  31. data/lib/puma/const.rb +13 -37
  32. data/lib/puma/control_cli.rb +38 -35
  33. data/lib/puma/convenient.rb +3 -3
  34. data/lib/puma/detect.rb +3 -1
  35. data/lib/puma/dsl.rb +80 -58
  36. data/lib/puma/events.rb +6 -8
  37. data/lib/puma/io_buffer.rb +1 -1
  38. data/lib/puma/jruby_restart.rb +0 -1
  39. data/lib/puma/launcher.rb +52 -30
  40. data/lib/puma/minissl.rb +73 -4
  41. data/lib/puma/null_io.rb +6 -13
  42. data/lib/puma/plugin/tmp_restart.rb +1 -2
  43. data/lib/puma/rack/builder.rb +3 -0
  44. data/lib/puma/rack/urlmap.rb +9 -8
  45. data/lib/puma/reactor.rb +135 -0
  46. data/lib/puma/runner.rb +23 -1
  47. data/lib/puma/server.rb +117 -34
  48. data/lib/puma/single.rb +14 -3
  49. data/lib/puma/thread_pool.rb +67 -20
  50. data/lib/puma/util.rb +1 -5
  51. data/lib/rack/handler/puma.rb +58 -17
  52. data/tools/jungle/README.md +12 -2
  53. data/tools/jungle/init.d/README.md +9 -2
  54. data/tools/jungle/init.d/puma +32 -62
  55. data/tools/jungle/init.d/run-puma +5 -1
  56. data/tools/jungle/rc.d/README.md +74 -0
  57. data/tools/jungle/rc.d/puma +61 -0
  58. data/tools/jungle/rc.d/puma.conf +10 -0
  59. data/tools/trickletest.rb +1 -1
  60. metadata +22 -92
  61. data/Gemfile +0 -13
  62. data/Manifest.txt +0 -77
  63. data/Rakefile +0 -158
  64. data/lib/puma/rack/backports/uri/common_18.rb +0 -59
  65. data/lib/puma/rack/backports/uri/common_192.rb +0 -55
  66. data/puma.gemspec +0 -52
@@ -43,15 +43,17 @@ module Puma::Rack
43
43
  def call(env)
44
44
  path = env['PATH_INFO']
45
45
  script_name = env['SCRIPT_NAME']
46
- hHost = env['HTTP_HOST']
47
- sName = env['SERVER_NAME']
48
- sPort = env['SERVER_PORT']
46
+ http_host = env['HTTP_HOST']
47
+ server_name = env['SERVER_NAME']
48
+ server_port = env['SERVER_PORT']
49
+
50
+ is_same_server = casecmp?(http_host, server_name) ||
51
+ casecmp?(http_host, "#{server_name}:#{server_port}")
49
52
 
50
53
  @mapping.each do |host, location, match, app|
51
- unless casecmp?(hHost, host) \
52
- || casecmp?(sName, host) \
53
- || (!host && (casecmp?(hHost, sName) ||
54
- casecmp?(hHost, sName+':'+sPort)))
54
+ unless casecmp?(http_host, host) \
55
+ || casecmp?(server_name, host) \
56
+ || (!host && is_same_server)
55
57
  next
56
58
  end
57
59
 
@@ -87,4 +89,3 @@ module Puma::Rack
87
89
  end
88
90
  end
89
91
  end
90
-
@@ -2,15 +2,54 @@ require 'puma/util'
2
2
  require 'puma/minissl'
3
3
 
4
4
  module Puma
5
+ # Internal Docs, Not a public interface.
6
+ #
7
+ # The Reactor object is responsible for ensuring that a request has been
8
+ # completely received before it starts to be processed. This may be known as read buffering.
9
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
10
+ # such as nginx) then the application would be subject to a slow client attack.
11
+ #
12
+ # Each Puma "worker" process has its own Reactor. For example if you start puma with `$ puma -w 5` then
13
+ # it will have 5 workers and each worker will have it's own reactor.
14
+ #
15
+ # For a graphical representation of how the reactor works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
16
+ #
17
+ # ## Reactor Flow
18
+ #
19
+ # A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
20
+ # The reactor stores the request in an array and calls `IO.select` on the array in a loop.
21
+ #
22
+ # When the request is written to by the client then the `IO.select` will "wake up" and
23
+ # return the references to any objects that caused it to "wake". The reactor
24
+ # then loops through each of these request objects, and sees if they're complete. If they
25
+ # have a full header and body then the reactor passes the request to a thread pool.
26
+ # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
27
+ #
28
+ # If the request is not complete, then it stays in the array, and the next time any
29
+ # data is written to that socket reference, then the loop is woken up and it is checked for completeness again.
30
+ #
31
+ # A detailed example is given in the docs for `run_internal` which is where the bulk
32
+ # of this logic lives.
5
33
  class Reactor
6
34
  DefaultSleepFor = 5
7
35
 
36
+ # Creates an instance of Puma::Reactor
37
+ #
38
+ # The `server` argument is an instance of `Puma::Server`
39
+ # this is used to write a response for "low level errors"
40
+ # when there is an exception inside of the reactor.
41
+ #
42
+ # The `app_pool` is an instance of `Puma::ThreadPool`.
43
+ # Once a request is fully formed (header and body are received)
44
+ # it will be passed to the `app_pool`.
8
45
  def initialize(server, app_pool)
9
46
  @server = server
10
47
  @events = server.events
11
48
  @app_pool = app_pool
12
49
 
13
50
  @mutex = Mutex.new
51
+
52
+ # Read / Write pipes to wake up internal while loop
14
53
  @ready, @trigger = Puma::Util.pipe
15
54
  @input = []
16
55
  @sleep_for = DefaultSleepFor
@@ -21,6 +60,64 @@ module Puma
21
60
 
22
61
  private
23
62
 
63
+
64
+ # Until a request is added via the `add` method this method will internally
65
+ # loop, waiting on the `sockets` array objects. The only object in this
66
+ # array at first is the `@ready` IO object, which is the read end of a pipe
67
+ # connected to `@trigger` object. When `@trigger` is written to, then the loop
68
+ # will break on `IO.select` and return an array.
69
+ #
70
+ # ## When a request is added:
71
+ #
72
+ # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
73
+ # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
74
+ #
75
+ # When that happens, the internal loop stops blocking at `IO.select` and returns a reference
76
+ # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
77
+ # When `@trigger` is written-to, the loop "wakes" and the `ready`
78
+ # variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
79
+ # first IO object is the `@ready` object. This first array `[#<IO:fd 10>]`
80
+ # is saved as a `reads` variable.
81
+ #
82
+ # The `reads` variable is iterated through. In the case that the object
83
+ # is the same as the `@ready` input pipe, then we know that there was a `trigger` event.
84
+ #
85
+ # If there was a trigger event, then one byte of `@ready` is read into memory. In the case of the first request,
86
+ # the reactor sees that it's a `"*"` value and the reactor adds the contents of `@input` into the `sockets` array.
87
+ # The while then loop continues to iterate again, but now the `sockets` array contains a `Puma::Client` instance in addition
88
+ # to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
89
+ #
90
+ # Since the `Puma::Client` in this example has data that has not been read yet,
91
+ # the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
92
+ # `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
93
+ #
94
+ # Each element in the first entry is iterated over. The `Puma::Client` object is not
95
+ # the `@ready` pipe, so the reactor checks to see if it has the fully header and body with
96
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
97
+ # then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
98
+ # can pick up the request and begin to execute application logic. This is done
99
+ # via `@app_pool << c`. The `Puma::Client` is then removed from the `sockets` array.
100
+ #
101
+ # If the request body is not present then nothing will happen, and the loop will iterate
102
+ # again. When the client sends more data to the socket the `Puma::Client` object will
103
+ # wake up the `IO.select` and it can again be checked to see if it's ready to be
104
+ # passed to the thread pool.
105
+ #
106
+ # ## Time Out Case
107
+ #
108
+ # In addition to being woken via a write to one of the sockets the `IO.select` will
109
+ # periodically "time out" of the sleep. One of the functions of this is to check for
110
+ # any requests that have "timed out". At the end of the loop it's checked to see if
111
+ # the first element in the `@timeout` array has exceed it's allowed time. If so,
112
+ # the client object is removed from the timeout aray, a 408 response is written.
113
+ # Then it's connection is closed, and the object is removed from the `sockets` array
114
+ # that watches for new data.
115
+ #
116
+ # This behavior loops until all the objects that have timed out have been removed.
117
+ #
118
+ # Once all the timeouts have been processed, the next duration of the `IO.select` sleep
119
+ # will be set to be equal to the amount of time it will take for the next timeout to occur.
120
+ # This calculation happens in `calculate_sleep`.
24
121
  def run_internal
25
122
  sockets = @sockets
26
123
 
@@ -28,6 +125,7 @@ module Puma
28
125
  begin
29
126
  ready = IO.select sockets, nil, nil, @sleep_for
30
127
  rescue IOError => e
128
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
31
129
  if sockets.any? { |socket| socket.closed? }
32
130
  STDERR.puts "Error in select: #{e.message} (#{e.class})"
33
131
  STDERR.puts e.backtrace
@@ -162,6 +260,16 @@ module Puma
162
260
  end
163
261
  end
164
262
 
263
+ # The `calculate_sleep` sets the value that the `IO.select` will
264
+ # sleep for in the main reactor loop when no sockets are being written to.
265
+ #
266
+ # The values kept in `@timeouts` are sorted so that the first timeout
267
+ # comes first in the array. When there are no timeouts the default timeout is used.
268
+ #
269
+ # Otherwise a sleep value is set that is the same as the amount of time it
270
+ # would take for the first element to time out.
271
+ #
272
+ # If that value is in the past, then a sleep value of zero is used.
165
273
  def calculate_sleep
166
274
  if @timeouts.empty?
167
275
  @sleep_for = DefaultSleepFor
@@ -176,6 +284,31 @@ module Puma
176
284
  end
177
285
  end
178
286
 
287
+ # This method adds a connection to the reactor
288
+ #
289
+ # Typically called by `Puma::Server` the value passed in
290
+ # is usually a `Puma::Client` object that responds like an IO
291
+ # object.
292
+ #
293
+ # The main body of the reactor loop is in `run_internal` and it
294
+ # will sleep on `IO.select`. When a new connection is added to the
295
+ # reactor it cannot be added directly to the `sockets` aray, because
296
+ # the `IO.select` will not be watching for it yet.
297
+ #
298
+ # Instead what needs to happen is that `IO.select` needs to be woken up,
299
+ # the contents of `@input` added to the `sockets` array, and then
300
+ # another call to `IO.select` needs to happen. Since the `Puma::Client`
301
+ # object can be read immediately, it does not block, but instead returns
302
+ # right away.
303
+ #
304
+ # This behavior is accomplished by writing to `@trigger` which wakes up
305
+ # the `IO.select` and then there is logic to detect the value of `*`,
306
+ # pull the contents from `@input` and add them to the sockets array.
307
+ #
308
+ # If the object passed in has a timeout value in `timeout_at` then
309
+ # it is added to a `@timeouts` array. This array is then re-arranged
310
+ # so that the first element to timeout will be at the front of the
311
+ # array. Then a value to sleep for is derived in the call to `calculate_sleep`
179
312
  def add(c)
180
313
  @mutex.synchronize do
181
314
  @input << c
@@ -195,6 +328,7 @@ module Puma
195
328
  begin
196
329
  @trigger << "c"
197
330
  rescue IOError
331
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
198
332
  end
199
333
  end
200
334
 
@@ -202,6 +336,7 @@ module Puma
202
336
  begin
203
337
  @trigger << "!"
204
338
  rescue IOError
339
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
205
340
  end
206
341
 
207
342
  @thread.join
@@ -1,4 +1,10 @@
1
+ require 'puma/server'
2
+ require 'puma/const'
3
+
1
4
  module Puma
5
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
6
+ # serve requests. This class spawns a new instance of `Puma::Server` via
7
+ # a call to `start_server`.
2
8
  class Runner
3
9
  def initialize(cli, events)
4
10
  @launcher = cli
@@ -16,6 +22,10 @@ module Puma
16
22
  @options[:environment] == "development"
17
23
  end
18
24
 
25
+ def test?
26
+ @options[:environment] == "test"
27
+ end
28
+
19
29
  def log(str)
20
30
  @events.log str
21
31
  end
@@ -104,12 +114,20 @@ module Puma
104
114
  append = @options[:redirect_append]
105
115
 
106
116
  if stdout
117
+ unless Dir.exist?(File.dirname(stdout))
118
+ raise "Cannot redirect STDOUT to #{stdout}"
119
+ end
120
+
107
121
  STDOUT.reopen stdout, (append ? "a" : "w")
108
122
  STDOUT.sync = true
109
123
  STDOUT.puts "=== puma startup: #{Time.now} ==="
110
124
  end
111
125
 
112
126
  if stderr
127
+ unless Dir.exist?(File.dirname(stderr))
128
+ raise "Cannot redirect STDERR to #{stderr}"
129
+ end
130
+
113
131
  STDERR.reopen stderr, (append ? "a" : "w")
114
132
  STDERR.sync = true
115
133
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -150,7 +168,11 @@ module Puma
150
168
  server.tcp_mode!
151
169
  end
152
170
 
153
- unless development?
171
+ if @options[:early_hints]
172
+ server.early_hints = true
173
+ end
174
+
175
+ unless development? || test?
154
176
  server.leak_stack_on_error = false
155
177
  end
156
178
 
@@ -23,6 +23,15 @@ require 'socket'
23
23
  module Puma
24
24
 
25
25
  # The HTTP Server itself. Serves out a single Rack app.
26
+ #
27
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
28
+ # to generate one or more `Puma::Server` instances capable of handling requests.
29
+ # Each Puma process will contain one `Puma::Server` instacne.
30
+ #
31
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
32
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
33
+ #
34
+ # Each `Puma::Server` will have one reactor and one thread pool.
26
35
  class Server
27
36
 
28
37
  include Puma::Const
@@ -57,19 +66,19 @@ module Puma
57
66
 
58
67
  @min_threads = 0
59
68
  @max_threads = 16
60
- @auto_trim_time = 1
69
+ @auto_trim_time = 30
61
70
  @reaping_time = 1
62
71
 
63
72
  @thread = nil
64
73
  @thread_pool = nil
74
+ @early_hints = nil
65
75
 
66
76
  @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
77
+ @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
67
78
 
68
79
  @binder = Binder.new(events)
69
80
  @own_binder = true
70
81
 
71
- @first_data_timeout = FIRST_DATA_TIMEOUT
72
-
73
82
  @leak_stack_on_error = true
74
83
 
75
84
  @options = options
@@ -78,9 +87,11 @@ module Puma
78
87
  ENV['RACK_ENV'] ||= "development"
79
88
 
80
89
  @mode = :http
90
+
91
+ @precheck_closing = true
81
92
  end
82
93
 
83
- attr_accessor :binder, :leak_stack_on_error
94
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
84
95
 
85
96
  forward :add_tcp_listener, :@binder
86
97
  forward :add_ssl_listener, :@binder
@@ -100,6 +111,8 @@ module Puma
100
111
  # packetizes our stream. This improves both latency and throughput.
101
112
  #
102
113
  if RUBY_PLATFORM =~ /linux/
114
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
115
+
103
116
  # 6 == Socket::IPPROTO_TCP
104
117
  # 3 == TCP_CORK
105
118
  # 1/0 == turn on/off
@@ -107,6 +120,7 @@ module Puma
107
120
  begin
108
121
  socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
109
122
  rescue IOError, SystemCallError
123
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
110
124
  end
111
125
  end
112
126
 
@@ -114,6 +128,24 @@ module Puma
114
128
  begin
115
129
  socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
116
130
  rescue IOError, SystemCallError
131
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
132
+ end
133
+ end
134
+
135
+ def closed_socket?(socket)
136
+ return false unless socket.kind_of? TCPSocket
137
+ return false unless @precheck_closing
138
+
139
+ begin
140
+ tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
141
+ rescue IOError, SystemCallError
142
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
143
+ @precheck_closing = false
144
+ false
145
+ else
146
+ state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
147
+ # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
148
+ (state >= 6 && state <= 9) || state == 11
117
149
  end
118
150
  end
119
151
  else
@@ -122,6 +154,10 @@ module Puma
122
154
 
123
155
  def uncork_socket(socket)
124
156
  end
157
+
158
+ def closed_socket?(socket)
159
+ false
160
+ end
125
161
  end
126
162
 
127
163
  def backlog
@@ -132,6 +168,18 @@ module Puma
132
168
  @thread_pool and @thread_pool.spawned
133
169
  end
134
170
 
171
+
172
+ # This number represents the number of requests that
173
+ # the server is capable of taking right now.
174
+ #
175
+ # For example if the number is 5 then it means
176
+ # there are 5 threads sitting idle ready to take
177
+ # a request. If one request comes in, then the
178
+ # value would be 4 until it finishes processing.
179
+ def pool_capacity
180
+ @thread_pool and @thread_pool.pool_capacity
181
+ end
182
+
135
183
  # Lopez Mode == raw tcp apps
136
184
 
137
185
  def run_lopez_mode(background=true)
@@ -193,7 +241,11 @@ module Puma
193
241
  # nothing
194
242
  rescue Errno::ECONNABORTED
195
243
  # client closed the socket even before accept
196
- io.close rescue nil
244
+ begin
245
+ io.close
246
+ rescue
247
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
248
+ end
197
249
  end
198
250
  end
199
251
  end
@@ -210,7 +262,12 @@ module Puma
210
262
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
211
263
  STDERR.puts e.backtrace
212
264
  ensure
213
- @check.close
265
+ begin
266
+ @check.close
267
+ rescue
268
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
269
+ end
270
+
214
271
  @notify.close
215
272
 
216
273
  if @status != :restart and @own_binder
@@ -268,7 +325,7 @@ module Puma
268
325
  client.close
269
326
 
270
327
  @events.parse_error self, client.env, e
271
- rescue ConnectionError
328
+ rescue ConnectionError, EOFError
272
329
  client.close
273
330
  else
274
331
  if process_now
@@ -339,13 +396,17 @@ module Puma
339
396
  end
340
397
 
341
398
  pool << client
342
- pool.wait_until_not_full unless queue_requests
399
+ pool.wait_until_not_full
343
400
  end
344
401
  rescue SystemCallError
345
402
  # nothing
346
403
  rescue Errno::ECONNABORTED
347
404
  # client closed the socket even before accept
348
- io.close rescue nil
405
+ begin
406
+ io.close
407
+ rescue
408
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
409
+ end
349
410
  end
350
411
  end
351
412
  end
@@ -358,7 +419,7 @@ module Puma
358
419
 
359
420
  graceful_shutdown if @status == :stop || @status == :restart
360
421
  if queue_requests
361
- @reactor.clear! if @status == :restart
422
+ @reactor.clear!
362
423
  @reactor.shutdown
363
424
  end
364
425
  rescue Exception => e
@@ -404,10 +465,6 @@ module Puma
404
465
  def process_client(client, buffer)
405
466
  begin
406
467
 
407
- if client.env[HTTP_EXPECT] == CONTINUE
408
- client.io << HTTP_11_100
409
- end
410
-
411
468
  clean_thread_locals = @options[:clean_thread_locals]
412
469
  close_socket = true
413
470
 
@@ -471,6 +528,7 @@ module Puma
471
528
  begin
472
529
  client.close if close_socket
473
530
  rescue IOError, SystemCallError
531
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
474
532
  # Already closed
475
533
  rescue StandardError => e
476
534
  @events.unknown_error self, e, "Client"
@@ -502,7 +560,9 @@ module Puma
502
560
 
503
561
  raise "No REQUEST PATH" unless env[REQUEST_PATH]
504
562
 
505
- env[QUERY_STRING] = uri.query
563
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
564
+ # so only set the env value if there actually is a value.
565
+ env[QUERY_STRING] = uri.query if uri.query
506
566
  end
507
567
 
508
568
  env[PATH_INFO] = env[REQUEST_PATH]
@@ -550,6 +610,8 @@ module Puma
550
610
  env = req.env
551
611
  client = req.io
552
612
 
613
+ return false if closed_socket?(client)
614
+
553
615
  normalize_env env, req
554
616
 
555
617
  env[PUMA_SOCKET] = client
@@ -568,6 +630,24 @@ module Puma
568
630
  env[RACK_INPUT] = body
569
631
  env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
570
632
 
633
+ if @early_hints
634
+ env[EARLY_HINTS] = lambda { |headers|
635
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
636
+
637
+ headers.each_pair do |k, vs|
638
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
639
+ vs.to_s.split(NEWLINE).each do |v|
640
+ fast_write client, "#{k}: #{v}\r\n"
641
+ end
642
+ else
643
+ fast_write client, "#{k}: #{vs}\r\n"
644
+ end
645
+ end
646
+
647
+ fast_write client, "\r\n".freeze
648
+ }
649
+ end
650
+
571
651
  # A rack extension. If the app writes #call'ables to this
572
652
  # array, we will invoke them when the request is done.
573
653
  #
@@ -665,7 +745,7 @@ module Puma
665
745
  next
666
746
  end
667
747
 
668
- if vs.respond_to?(:to_s)
748
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
669
749
  vs.to_s.split(NEWLINE).each do |v|
670
750
  lines.append k, colon, v, line_ending
671
751
  end
@@ -709,8 +789,8 @@ module Puma
709
789
 
710
790
  begin
711
791
  res_body.each do |part|
792
+ next if part.bytesize.zero?
712
793
  if chunked
713
- next if part.bytesize.zero?
714
794
  fast_write client, part.bytesize.to_s(16)
715
795
  fast_write client, line_ending
716
796
  fast_write client, part
@@ -869,35 +949,38 @@ module Puma
869
949
  end
870
950
  end
871
951
 
872
- # Stops the acceptor thread and then causes the worker threads to finish
873
- # off the request queue before finally exiting.
874
- #
875
- def stop(sync=false)
952
+ def notify_safely(message)
876
953
  begin
877
- @notify << STOP_COMMAND
954
+ @notify << message
878
955
  rescue IOError
879
- # The server, in another thread, is shutting down
956
+ # The server, in another thread, is shutting down
957
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
958
+ rescue RuntimeError => e
959
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
960
+ if e.message.include?('IOError')
961
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
962
+ else
963
+ raise e
964
+ end
880
965
  end
966
+ end
967
+ private :notify_safely
881
968
 
969
+ # Stops the acceptor thread and then causes the worker threads to finish
970
+ # off the request queue before finally exiting.
971
+
972
+ def stop(sync=false)
973
+ notify_safely(STOP_COMMAND)
882
974
  @thread.join if @thread && sync
883
975
  end
884
976
 
885
977
  def halt(sync=false)
886
- begin
887
- @notify << HALT_COMMAND
888
- rescue IOError
889
- # The server, in another thread, is shutting down
890
- end
891
-
978
+ notify_safely(HALT_COMMAND)
892
979
  @thread.join if @thread && sync
893
980
  end
894
981
 
895
982
  def begin_restart
896
- begin
897
- @notify << RESTART_COMMAND
898
- rescue IOError
899
- # The server, in another thread, is shutting down
900
- end
983
+ notify_safely(RESTART_COMMAND)
901
984
  end
902
985
 
903
986
  def fast_write(io, str)