puma 3.4.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 (71) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +356 -74
  3. data/README.md +143 -227
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
  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 +291 -447
  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/http11_parser_common.rl +1 -1
  18. data/ext/puma_http11/io_buffer.c +7 -7
  19. data/ext/puma_http11/mini_ssl.c +67 -6
  20. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +76 -94
  21. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
  22. data/ext/puma_http11/puma_http11.c +1 -0
  23. data/lib/puma.rb +13 -5
  24. data/lib/puma/app/status.rb +8 -0
  25. data/lib/puma/binder.rb +46 -21
  26. data/lib/puma/cli.rb +49 -33
  27. data/lib/puma/client.rb +149 -4
  28. data/lib/puma/cluster.rb +55 -13
  29. data/lib/puma/commonlogger.rb +19 -20
  30. data/lib/puma/compat.rb +3 -7
  31. data/lib/puma/configuration.rb +136 -131
  32. data/lib/puma/const.rb +19 -37
  33. data/lib/puma/control_cli.rb +38 -35
  34. data/lib/puma/convenient.rb +3 -3
  35. data/lib/puma/detect.rb +3 -1
  36. data/lib/puma/dsl.rb +86 -57
  37. data/lib/puma/events.rb +17 -13
  38. data/lib/puma/io_buffer.rb +1 -1
  39. data/lib/puma/jruby_restart.rb +0 -1
  40. data/lib/puma/launcher.rb +61 -30
  41. data/lib/puma/minissl.rb +85 -4
  42. data/lib/puma/null_io.rb +6 -13
  43. data/lib/puma/plugin.rb +12 -1
  44. data/lib/puma/plugin/tmp_restart.rb +1 -2
  45. data/lib/puma/rack/builder.rb +3 -0
  46. data/lib/puma/rack/urlmap.rb +9 -8
  47. data/lib/puma/reactor.rb +144 -0
  48. data/lib/puma/runner.rb +27 -1
  49. data/lib/puma/server.rb +135 -33
  50. data/lib/puma/single.rb +17 -3
  51. data/lib/puma/tcp_logger.rb +8 -1
  52. data/lib/puma/thread_pool.rb +70 -20
  53. data/lib/puma/util.rb +1 -5
  54. data/lib/rack/handler/puma.rb +58 -17
  55. data/tools/jungle/README.md +12 -2
  56. data/tools/jungle/init.d/README.md +9 -2
  57. data/tools/jungle/init.d/puma +85 -58
  58. data/tools/jungle/init.d/run-puma +16 -1
  59. data/tools/jungle/rc.d/README.md +74 -0
  60. data/tools/jungle/rc.d/puma +61 -0
  61. data/tools/jungle/rc.d/puma.conf +10 -0
  62. data/tools/jungle/upstart/puma.conf +1 -1
  63. data/tools/trickletest.rb +1 -1
  64. metadata +22 -94
  65. data/Gemfile +0 -13
  66. data/Manifest.txt +0 -78
  67. data/Rakefile +0 -158
  68. data/docs/config.md +0 -0
  69. data/lib/puma/rack/backports/uri/common_18.rb +0 -59
  70. data/lib/puma/rack/backports/uri/common_192.rb +0 -55
  71. data/puma.gemspec +0 -52
@@ -1,42 +1,35 @@
1
1
  module Puma
2
-
3
2
  # Provides an IO-like object that always appears to contain no data.
4
3
  # Used as the value for rack.input when the request has no body.
5
4
  #
6
5
  class NullIO
7
- # Always returns nil
8
- #
9
6
  def gets
10
7
  nil
11
8
  end
12
9
 
13
- # Never yields
14
- #
15
10
  def each
16
11
  end
17
12
 
18
- # Mimics IO#read with no data
13
+ # Mimics IO#read with no data.
19
14
  #
20
- def read(count=nil,buffer=nil)
15
+ def read(count = nil, _buffer = nil)
21
16
  (count && count > 0) ? nil : ""
22
17
  end
23
18
 
24
- # Does nothing
25
- #
26
19
  def rewind
27
20
  end
28
21
 
29
- # Does nothing
30
- #
31
22
  def close
32
23
  end
33
24
 
34
- # Always zero
35
- #
36
25
  def size
37
26
  0
38
27
  end
39
28
 
29
+ def eof?
30
+ true
31
+ end
32
+
40
33
  def sync=(v)
41
34
  end
42
35
 
@@ -28,6 +28,7 @@ module Puma
28
28
  class PluginRegistry
29
29
  def initialize
30
30
  @plugins = {}
31
+ @background = []
31
32
  end
32
33
 
33
34
  def register(name, cls)
@@ -53,6 +54,16 @@ module Puma
53
54
 
54
55
  raise UnknownPlugin, "file failed to register a plugin"
55
56
  end
57
+
58
+ def add_background(blk)
59
+ @background << blk
60
+ end
61
+
62
+ def fire_background
63
+ @background.each do |b|
64
+ Thread.new(&b)
65
+ end
66
+ end
56
67
  end
57
68
 
58
69
  Plugins = PluginRegistry.new
@@ -93,7 +104,7 @@ module Puma
93
104
  end
94
105
 
95
106
  def in_background(&blk)
96
- Thread.new(&blk)
107
+ Plugins.add_background blk
97
108
  end
98
109
 
99
110
  def workers_supported?
@@ -8,7 +8,7 @@ Puma::Plugin.create do
8
8
 
9
9
  # If we can't write to the path, then just don't bother with this plugin
10
10
  begin
11
- File.write path, ""
11
+ File.write(path, "") unless File.exist?(path)
12
12
  orig = File.stat(path).mtime
13
13
  rescue SystemCallError
14
14
  return
@@ -32,4 +32,3 @@ Puma::Plugin.create do
32
32
  end
33
33
  end
34
34
  end
35
-
@@ -1,3 +1,6 @@
1
+ module Puma
2
+ end
3
+
1
4
  module Puma::Rack
2
5
  class Options
3
6
  def parse!(args)
@@ -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
@@ -75,6 +173,15 @@ module Puma
75
173
  sockets.delete c
76
174
  end
77
175
 
176
+ # Don't report these to the lowlevel_error handler, otherwise
177
+ # will be flooding them with errors when persistent connections
178
+ # are closed.
179
+ rescue ConnectionError
180
+ c.write_500
181
+ c.close
182
+
183
+ sockets.delete c
184
+
78
185
  # SSL handshake failure
79
186
  rescue MiniSSL::SSLError => e
80
187
  @server.lowlevel_error(e, c.env)
@@ -153,6 +260,16 @@ module Puma
153
260
  end
154
261
  end
155
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.
156
273
  def calculate_sleep
157
274
  if @timeouts.empty?
158
275
  @sleep_for = DefaultSleepFor
@@ -167,6 +284,31 @@ module Puma
167
284
  end
168
285
  end
169
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`
170
312
  def add(c)
171
313
  @mutex.synchronize do
172
314
  @input << c
@@ -186,6 +328,7 @@ module Puma
186
328
  begin
187
329
  @trigger << "c"
188
330
  rescue IOError
331
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
189
332
  end
190
333
  end
191
334
 
@@ -193,6 +336,7 @@ module Puma
193
336
  begin
194
337
  @trigger << "!"
195
338
  rescue IOError
339
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
196
340
  end
197
341
 
198
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
@@ -94,18 +104,30 @@ module Puma
94
104
  end
95
105
  end
96
106
 
107
+ def redirected_io?
108
+ @options[:redirect_stdout] || @options[:redirect_stderr]
109
+ end
110
+
97
111
  def redirect_io
98
112
  stdout = @options[:redirect_stdout]
99
113
  stderr = @options[:redirect_stderr]
100
114
  append = @options[:redirect_append]
101
115
 
102
116
  if stdout
117
+ unless Dir.exist?(File.dirname(stdout))
118
+ raise "Cannot redirect STDOUT to #{stdout}"
119
+ end
120
+
103
121
  STDOUT.reopen stdout, (append ? "a" : "w")
104
122
  STDOUT.sync = true
105
123
  STDOUT.puts "=== puma startup: #{Time.now} ==="
106
124
  end
107
125
 
108
126
  if stderr
127
+ unless Dir.exist?(File.dirname(stderr))
128
+ raise "Cannot redirect STDERR to #{stderr}"
129
+ end
130
+
109
131
  STDERR.reopen stderr, (append ? "a" : "w")
110
132
  STDERR.sync = true
111
133
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -146,7 +168,11 @@ module Puma
146
168
  server.tcp_mode!
147
169
  end
148
170
 
149
- unless development?
171
+ if @options[:early_hints]
172
+ server.early_hints = true
173
+ end
174
+
175
+ unless development? || test?
150
176
  server.leak_stack_on_error = false
151
177
  end
152
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
- @persistent_timeout = PERSISTENT_TIMEOUT
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
@@ -242,6 +299,10 @@ module Puma
242
299
  @thread_pool = ThreadPool.new(@min_threads,
243
300
  @max_threads,
244
301
  IOBuffer) do |client, buffer|
302
+
303
+ # Advertise this server into the thread
304
+ Thread.current[ThreadLocalKey] = self
305
+
245
306
  process_now = false
246
307
 
247
308
  begin
@@ -264,7 +325,7 @@ module Puma
264
325
  client.close
265
326
 
266
327
  @events.parse_error self, client.env, e
267
- rescue ConnectionError
328
+ rescue ConnectionError, EOFError
268
329
  client.close
269
330
  else
270
331
  if process_now
@@ -335,13 +396,17 @@ module Puma
335
396
  end
336
397
 
337
398
  pool << client
338
- pool.wait_until_not_full unless queue_requests
399
+ pool.wait_until_not_full
339
400
  end
340
401
  rescue SystemCallError
341
402
  # nothing
342
403
  rescue Errno::ECONNABORTED
343
404
  # client closed the socket even before accept
344
- 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
345
410
  end
346
411
  end
347
412
  end
@@ -354,7 +419,7 @@ module Puma
354
419
 
355
420
  graceful_shutdown if @status == :stop || @status == :restart
356
421
  if queue_requests
357
- @reactor.clear! if @status == :restart
422
+ @reactor.clear!
358
423
  @reactor.shutdown
359
424
  end
360
425
  rescue Exception => e
@@ -399,6 +464,7 @@ module Puma
399
464
  #
400
465
  def process_client(client, buffer)
401
466
  begin
467
+
402
468
  clean_thread_locals = @options[:clean_thread_locals]
403
469
  close_socket = true
404
470
 
@@ -462,6 +528,7 @@ module Puma
462
528
  begin
463
529
  client.close if close_socket
464
530
  rescue IOError, SystemCallError
531
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
465
532
  # Already closed
466
533
  rescue StandardError => e
467
534
  @events.unknown_error self, e, "Client"
@@ -493,7 +560,9 @@ module Puma
493
560
 
494
561
  raise "No REQUEST PATH" unless env[REQUEST_PATH]
495
562
 
496
- 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
497
566
  end
498
567
 
499
568
  env[PATH_INFO] = env[REQUEST_PATH]
@@ -541,6 +610,8 @@ module Puma
541
610
  env = req.env
542
611
  client = req.io
543
612
 
613
+ return false if closed_socket?(client)
614
+
544
615
  normalize_env env, req
545
616
 
546
617
  env[PUMA_SOCKET] = client
@@ -559,6 +630,24 @@ module Puma
559
630
  env[RACK_INPUT] = body
560
631
  env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
561
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
+
562
651
  # A rack extension. If the app writes #call'ables to this
563
652
  # array, we will invoke them when the request is done.
564
653
  #
@@ -587,8 +676,8 @@ module Puma
587
676
  headers = {}
588
677
  res_body = ["Request was internally terminated early\n"]
589
678
 
590
- rescue StandardError => e
591
- @events.unknown_error self, e, "Rack app"
679
+ rescue Exception => e
680
+ @events.unknown_error self, e, "Rack app", env
592
681
 
593
682
  status, headers, res_body = lowlevel_error(e, env)
594
683
  end
@@ -656,7 +745,7 @@ module Puma
656
745
  next
657
746
  end
658
747
 
659
- if vs.respond_to?(:to_s)
748
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
660
749
  vs.to_s.split(NEWLINE).each do |v|
661
750
  lines.append k, colon, v, line_ending
662
751
  end
@@ -700,8 +789,8 @@ module Puma
700
789
 
701
790
  begin
702
791
  res_body.each do |part|
792
+ next if part.bytesize.zero?
703
793
  if chunked
704
- next if part.bytesize.zero?
705
794
  fast_write client, part.bytesize.to_s(16)
706
795
  fast_write client, line_ending
707
796
  fast_write client, part
@@ -860,35 +949,38 @@ module Puma
860
949
  end
861
950
  end
862
951
 
863
- # Stops the acceptor thread and then causes the worker threads to finish
864
- # off the request queue before finally exiting.
865
- #
866
- def stop(sync=false)
952
+ def notify_safely(message)
867
953
  begin
868
- @notify << STOP_COMMAND
954
+ @notify << message
869
955
  rescue IOError
870
- # 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
871
965
  end
966
+ end
967
+ private :notify_safely
968
+
969
+ # Stops the acceptor thread and then causes the worker threads to finish
970
+ # off the request queue before finally exiting.
872
971
 
972
+ def stop(sync=false)
973
+ notify_safely(STOP_COMMAND)
873
974
  @thread.join if @thread && sync
874
975
  end
875
976
 
876
977
  def halt(sync=false)
877
- begin
878
- @notify << HALT_COMMAND
879
- rescue IOError
880
- # The server, in another thread, is shutting down
881
- end
882
-
978
+ notify_safely(HALT_COMMAND)
883
979
  @thread.join if @thread && sync
884
980
  end
885
981
 
886
982
  def begin_restart
887
- begin
888
- @notify << RESTART_COMMAND
889
- rescue IOError
890
- # The server, in another thread, is shutting down
891
- end
983
+ notify_safely(RESTART_COMMAND)
892
984
  end
893
985
 
894
986
  def fast_write(io, str)
@@ -911,5 +1003,15 @@ module Puma
911
1003
  end
912
1004
  end
913
1005
  private :fast_write
1006
+
1007
+ ThreadLocalKey = :puma_server
1008
+
1009
+ def self.current
1010
+ Thread.current[ThreadLocalKey]
1011
+ end
1012
+
1013
+ def shutting_down?
1014
+ @status == :stop || @status == :restart
1015
+ end
914
1016
  end
915
1017
  end