puma 3.0.0.rc1 → 5.0.0.beta1

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 (91) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +703 -70
  3. data/LICENSE +23 -20
  4. data/README.md +173 -163
  5. data/docs/architecture.md +37 -0
  6. data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  9. data/docs/images/puma-connection-flow.png +0 -0
  10. data/docs/images/puma-general-arch.png +0 -0
  11. data/docs/jungle/README.md +13 -0
  12. data/docs/jungle/rc.d/README.md +74 -0
  13. data/docs/jungle/rc.d/puma +61 -0
  14. data/docs/jungle/rc.d/puma.conf +10 -0
  15. data/{tools → docs}/jungle/upstart/README.md +0 -0
  16. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  17. data/{tools → docs}/jungle/upstart/puma.conf +1 -1
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +38 -0
  20. data/docs/restart.md +41 -0
  21. data/docs/signals.md +57 -3
  22. data/docs/systemd.md +228 -0
  23. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  24. data/ext/puma_http11/extconf.rb +16 -0
  25. data/ext/puma_http11/http11_parser.c +287 -468
  26. data/ext/puma_http11/http11_parser.h +1 -0
  27. data/ext/puma_http11/http11_parser.java.rl +21 -37
  28. data/ext/puma_http11/http11_parser.rl +10 -9
  29. data/ext/puma_http11/http11_parser_common.rl +4 -4
  30. data/ext/puma_http11/mini_ssl.c +159 -10
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  34. data/ext/puma_http11/puma_http11.c +6 -38
  35. data/lib/puma.rb +25 -5
  36. data/lib/puma/accept_nonblock.rb +7 -1
  37. data/lib/puma/app/status.rb +53 -26
  38. data/lib/puma/binder.rb +150 -119
  39. data/lib/puma/cli.rb +56 -38
  40. data/lib/puma/client.rb +277 -80
  41. data/lib/puma/cluster.rb +326 -130
  42. data/lib/puma/commonlogger.rb +21 -20
  43. data/lib/puma/configuration.rb +160 -161
  44. data/lib/puma/const.rb +50 -47
  45. data/lib/puma/control_cli.rb +104 -63
  46. data/lib/puma/detect.rb +13 -1
  47. data/lib/puma/dsl.rb +463 -114
  48. data/lib/puma/events.rb +22 -13
  49. data/lib/puma/io_buffer.rb +9 -5
  50. data/lib/puma/jruby_restart.rb +2 -59
  51. data/lib/puma/launcher.rb +195 -105
  52. data/lib/puma/minissl.rb +110 -4
  53. data/lib/puma/minissl/context_builder.rb +76 -0
  54. data/lib/puma/null_io.rb +9 -14
  55. data/lib/puma/plugin.rb +32 -12
  56. data/lib/puma/plugin/tmp_restart.rb +19 -6
  57. data/lib/puma/rack/builder.rb +7 -5
  58. data/lib/puma/rack/urlmap.rb +11 -8
  59. data/lib/puma/rack_default.rb +2 -0
  60. data/lib/puma/reactor.rb +242 -32
  61. data/lib/puma/runner.rb +41 -30
  62. data/lib/puma/server.rb +265 -183
  63. data/lib/puma/single.rb +22 -63
  64. data/lib/puma/state_file.rb +9 -2
  65. data/lib/puma/thread_pool.rb +179 -68
  66. data/lib/puma/util.rb +3 -11
  67. data/lib/rack/handler/puma.rb +60 -11
  68. data/tools/Dockerfile +16 -0
  69. data/tools/trickletest.rb +1 -2
  70. metadata +35 -99
  71. data/COPYING +0 -55
  72. data/Gemfile +0 -13
  73. data/Manifest.txt +0 -79
  74. data/Rakefile +0 -158
  75. data/docs/config.md +0 -0
  76. data/ext/puma_http11/io_buffer.c +0 -155
  77. data/lib/puma/capistrano.rb +0 -94
  78. data/lib/puma/compat.rb +0 -18
  79. data/lib/puma/convenient.rb +0 -23
  80. data/lib/puma/daemon_ext.rb +0 -31
  81. data/lib/puma/delegation.rb +0 -11
  82. data/lib/puma/java_io_buffer.rb +0 -45
  83. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  84. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -32
  87. data/puma.gemspec +0 -52
  88. data/tools/jungle/README.md +0 -9
  89. data/tools/jungle/init.d/README.md +0 -54
  90. data/tools/jungle/init.d/puma +0 -394
  91. data/tools/jungle/init.d/run-puma +0 -3
@@ -1,4 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/server'
4
+ require 'puma/const'
5
+ require 'puma/minissl/context_builder'
6
+
1
7
  module Puma
8
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
9
+ # serve requests. This class spawns a new instance of `Puma::Server` via
10
+ # a call to `start_server`.
2
11
  class Runner
3
12
  def initialize(cli, events)
4
13
  @launcher = cli
@@ -6,16 +15,17 @@ module Puma
6
15
  @options = cli.options
7
16
  @app = nil
8
17
  @control = nil
9
- end
10
-
11
- def daemon?
12
- @options[:daemon]
18
+ @started_at = Time.now
13
19
  end
14
20
 
15
21
  def development?
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
@@ -38,41 +48,35 @@ module Puma
38
48
 
39
49
  require 'puma/app/status'
40
50
 
41
- uri = URI.parse str
42
-
43
- app = Puma::App::Status.new @launcher
44
-
45
51
  if token = @options[:control_auth_token]
46
- app.auth_token = token unless token.empty? or token == :none
52
+ token = nil if token.empty? || token == 'none'
47
53
  end
48
54
 
55
+ app = Puma::App::Status.new @launcher, token
56
+
49
57
  control = Puma::Server.new app, @launcher.events
50
58
  control.min_threads = 0
51
59
  control.max_threads = 1
52
60
 
53
- case uri.scheme
54
- when "tcp"
55
- log "* Starting control server on #{str}"
56
- control.add_tcp_listener uri.host, uri.port
57
- when "unix"
58
- log "* Starting control server on #{str}"
59
- path = "#{uri.host}#{uri.path}"
60
- mask = @options[:control_url_umask]
61
-
62
- control.add_unix_listener path, mask
63
- else
64
- error "Invalid control URI: #{str}"
65
- end
61
+ control.binder.parse [str], self, 'Starting control server'
66
62
 
67
63
  control.run
68
64
  @control = control
69
65
  end
70
66
 
67
+ def close_control_listeners
68
+ @control.binder.close_listeners if @control
69
+ end
70
+
71
71
  def ruby_engine
72
72
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
73
73
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
74
74
  else
75
- "#{RUBY_ENGINE} #{RUBY_VERSION}"
75
+ if defined?(RUBY_ENGINE_VERSION)
76
+ "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
77
+ else
78
+ "#{RUBY_ENGINE} #{RUBY_VERSION}"
79
+ end
76
80
  end
77
81
  end
78
82
 
@@ -84,10 +88,10 @@ module Puma
84
88
  log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
85
89
  log "* Min threads: #{min_t}, max threads: #{max_t}"
86
90
  log "* Environment: #{ENV['RACK_ENV']}"
91
+ end
87
92
 
88
- if @options[:mode] == :tcp
89
- log "* Mode: Lopez Express (tcp)"
90
- end
93
+ def redirected_io?
94
+ @options[:redirect_stdout] || @options[:redirect_stderr]
91
95
  end
92
96
 
93
97
  def redirect_io
@@ -96,12 +100,20 @@ module Puma
96
100
  append = @options[:redirect_append]
97
101
 
98
102
  if stdout
103
+ unless Dir.exist?(File.dirname(stdout))
104
+ raise "Cannot redirect STDOUT to #{stdout}"
105
+ end
106
+
99
107
  STDOUT.reopen stdout, (append ? "a" : "w")
100
108
  STDOUT.sync = true
101
109
  STDOUT.puts "=== puma startup: #{Time.now} ==="
102
110
  end
103
111
 
104
112
  if stderr
113
+ unless Dir.exist?(File.dirname(stderr))
114
+ raise "Cannot redirect STDERR to #{stderr}"
115
+ end
116
+
105
117
  STDERR.reopen stderr, (append ? "a" : "w")
106
118
  STDERR.sync = true
107
119
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -114,7 +126,6 @@ module Puma
114
126
  exit 1
115
127
  end
116
128
 
117
- # Load the app before we daemonize.
118
129
  begin
119
130
  @app = @launcher.config.app
120
131
  rescue Exception => e
@@ -138,11 +149,11 @@ module Puma
138
149
  server.max_threads = max_t
139
150
  server.inherit_binder @launcher.binder
140
151
 
141
- if @options[:mode] == :tcp
142
- server.tcp_mode!
152
+ if @options[:early_hints]
153
+ server.early_hints = true
143
154
  end
144
155
 
145
- unless development?
156
+ unless development? || test?
146
157
  server.leak_stack_on_error = false
147
158
  end
148
159
 
@@ -1,35 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
 
3
5
  require 'puma/thread_pool'
4
6
  require 'puma/const'
5
7
  require 'puma/events'
6
8
  require 'puma/null_io'
7
- require 'puma/compat'
8
9
  require 'puma/reactor'
9
10
  require 'puma/client'
10
11
  require 'puma/binder'
11
- require 'puma/delegation'
12
12
  require 'puma/accept_nonblock'
13
13
  require 'puma/util'
14
+ require 'puma/io_buffer'
14
15
 
15
16
  require 'puma/puma_http11'
16
17
 
17
- unless Puma.const_defined? "IOBuffer"
18
- require 'puma/io_buffer'
19
- end
20
-
21
18
  require 'socket'
19
+ require 'forwardable'
22
20
 
23
21
  module Puma
24
22
 
25
23
  # The HTTP Server itself. Serves out a single Rack app.
24
+ #
25
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
26
+ # to generate one or more `Puma::Server` instances capable of handling requests.
27
+ # Each Puma process will contain one `Puma::Server` instance.
28
+ #
29
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
30
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
31
+ #
32
+ # Each `Puma::Server` will have one reactor and one thread pool.
26
33
  class Server
27
34
 
28
35
  include Puma::Const
29
- extend Puma::Delegation
36
+ extend Forwardable
30
37
 
31
38
  attr_reader :thread
32
39
  attr_reader :events
40
+ attr_reader :requests_count
33
41
  attr_accessor :app
34
42
 
35
43
  attr_accessor :min_threads
@@ -51,24 +59,22 @@ module Puma
51
59
  @app = app
52
60
  @events = events
53
61
 
54
- @check, @notify = Puma::Util.pipe
55
-
62
+ @check, @notify = nil
56
63
  @status = :stop
57
64
 
58
65
  @min_threads = 0
59
66
  @max_threads = 16
60
- @auto_trim_time = 1
67
+ @auto_trim_time = 30
61
68
  @reaping_time = 1
62
69
 
63
70
  @thread = nil
64
71
  @thread_pool = nil
72
+ @early_hints = nil
65
73
 
66
- @persistent_timeout = PERSISTENT_TIMEOUT
74
+ @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
75
+ @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
67
76
 
68
77
  @binder = Binder.new(events)
69
- @own_binder = true
70
-
71
- @first_data_timeout = FIRST_DATA_TIMEOUT
72
78
 
73
79
  @leak_stack_on_error = true
74
80
 
@@ -78,28 +84,26 @@ module Puma
78
84
  ENV['RACK_ENV'] ||= "development"
79
85
 
80
86
  @mode = :http
87
+
88
+ @precheck_closing = true
89
+
90
+ @requests_count = 0
81
91
  end
82
92
 
83
- attr_accessor :binder, :leak_stack_on_error
93
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
84
94
 
85
- forward :add_tcp_listener, :@binder
86
- forward :add_ssl_listener, :@binder
87
- forward :add_unix_listener, :@binder
88
- forward :connected_port, :@binder
95
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
89
96
 
90
97
  def inherit_binder(bind)
91
98
  @binder = bind
92
- @own_binder = false
93
- end
94
-
95
- def tcp_mode!
96
- @mode = :tcp
97
99
  end
98
100
 
99
101
  # On Linux, use TCP_CORK to better control how the TCP stack
100
102
  # packetizes our stream. This improves both latency and throughput.
101
103
  #
102
104
  if RUBY_PLATFORM =~ /linux/
105
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
106
+
103
107
  # 6 == Socket::IPPROTO_TCP
104
108
  # 3 == TCP_CORK
105
109
  # 1/0 == turn on/off
@@ -107,6 +111,7 @@ module Puma
107
111
  begin
108
112
  socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
109
113
  rescue IOError, SystemCallError
114
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
110
115
  end
111
116
  end
112
117
 
@@ -114,6 +119,24 @@ module Puma
114
119
  begin
115
120
  socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
116
121
  rescue IOError, SystemCallError
122
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
123
+ end
124
+ end
125
+
126
+ def closed_socket?(socket)
127
+ return false unless socket.kind_of? TCPSocket
128
+ return false unless @precheck_closing
129
+
130
+ begin
131
+ tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
132
+ rescue IOError, SystemCallError
133
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
134
+ @precheck_closing = false
135
+ false
136
+ else
137
+ state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
138
+ # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
139
+ (state >= 6 && state <= 9) || state == 11
117
140
  end
118
141
  end
119
142
  else
@@ -122,6 +145,10 @@ module Puma
122
145
 
123
146
  def uncork_socket(socket)
124
147
  end
148
+
149
+ def closed_socket?(socket)
150
+ false
151
+ end
125
152
  end
126
153
 
127
154
  def backlog
@@ -132,94 +159,18 @@ module Puma
132
159
  @thread_pool and @thread_pool.spawned
133
160
  end
134
161
 
135
- # Lopez Mode == raw tcp apps
136
162
 
137
- def run_lopez_mode(background=true)
138
- @thread_pool = ThreadPool.new(@min_threads,
139
- @max_threads,
140
- Hash) do |client, tl|
141
-
142
- io = client.to_io
143
- addr = io.peeraddr.last
144
-
145
- if addr.empty?
146
- # Set unix socket addrs to localhost
147
- addr = "127.0.0.1:0"
148
- else
149
- addr = "#{addr}:#{io.peeraddr[1]}"
150
- end
151
-
152
- env = { 'thread' => tl, REMOTE_ADDR => addr }
153
-
154
- begin
155
- @app.call env, client.to_io
156
- rescue Object => e
157
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
158
- STDERR.puts e.backtrace
159
- end
160
-
161
- client.close unless env['detach']
162
- end
163
-
164
- @events.fire :state, :running
165
-
166
- if background
167
- @thread = Thread.new { handle_servers_lopez_mode }
168
- return @thread
169
- else
170
- handle_servers_lopez_mode
171
- end
163
+ # This number represents the number of requests that
164
+ # the server is capable of taking right now.
165
+ #
166
+ # For example if the number is 5 then it means
167
+ # there are 5 threads sitting idle ready to take
168
+ # a request. If one request comes in, then the
169
+ # value would be 4 until it finishes processing.
170
+ def pool_capacity
171
+ @thread_pool and @thread_pool.pool_capacity
172
172
  end
173
173
 
174
- def handle_servers_lopez_mode
175
- begin
176
- check = @check
177
- sockets = [check] + @binder.ios
178
- pool = @thread_pool
179
-
180
- while @status == :run
181
- begin
182
- ios = IO.select sockets
183
- ios.first.each do |sock|
184
- if sock == check
185
- break if handle_check
186
- else
187
- begin
188
- if io = sock.accept_nonblock
189
- client = Client.new io, nil
190
- pool << client
191
- end
192
- rescue SystemCallError
193
- # nothing
194
- rescue Errno::ECONNABORTED
195
- # client closed the socket even before accept
196
- io.close rescue nil
197
- end
198
- end
199
- end
200
- rescue Object => e
201
- @events.unknown_error self, e, "Listen loop"
202
- end
203
- end
204
-
205
- @events.fire :state, @status
206
-
207
- graceful_shutdown if @status == :stop || @status == :restart
208
-
209
- rescue Exception => e
210
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
211
- STDERR.puts e.backtrace
212
- ensure
213
- @check.close
214
- @notify.close
215
-
216
- if @status != :restart and @own_binder
217
- @binder.close
218
- end
219
- end
220
-
221
- @events.fire :state, :done
222
- end
223
174
  # Runs the server.
224
175
  #
225
176
  # If +background+ is true (the default) then a thread is spun
@@ -233,22 +184,20 @@ module Puma
233
184
 
234
185
  @status = :run
235
186
 
236
- if @mode == :tcp
237
- return run_lopez_mode(background)
238
- end
239
-
240
- queue_requests = @queue_requests
241
-
242
187
  @thread_pool = ThreadPool.new(@min_threads,
243
188
  @max_threads,
244
- IOBuffer) do |client, buffer|
189
+ ::Puma::IOBuffer) do |client, buffer|
190
+
191
+ # Advertise this server into the thread
192
+ Thread.current[ThreadLocalKey] = self
193
+
245
194
  process_now = false
246
195
 
247
196
  begin
248
- if queue_requests
197
+ if @queue_requests
249
198
  process_now = client.eagerly_finish
250
199
  else
251
- client.finish
200
+ client.finish(@first_data_timeout)
252
201
  process_now = true
253
202
  end
254
203
  rescue MiniSSL::SSLError => e
@@ -260,11 +209,11 @@ module Puma
260
209
 
261
210
  @events.ssl_error self, addr, cert, e
262
211
  rescue HttpParserError => e
263
- client.write_400
212
+ client.write_error(400)
264
213
  client.close
265
214
 
266
215
  @events.parse_error self, client.env, e
267
- rescue ConnectionError
216
+ rescue ConnectionError, EOFError
268
217
  client.close
269
218
  else
270
219
  if process_now
@@ -274,11 +223,14 @@ module Puma
274
223
  @reactor.add client
275
224
  end
276
225
  end
226
+
227
+ process_now
277
228
  end
278
229
 
230
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
279
231
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
280
232
 
281
- if queue_requests
233
+ if @queue_requests
282
234
  @reactor = Reactor.new self, @thread_pool
283
235
  @reactor.run_in_thread
284
236
  end
@@ -294,7 +246,10 @@ module Puma
294
246
  @events.fire :state, :running
295
247
 
296
248
  if background
297
- @thread = Thread.new { handle_servers }
249
+ @thread = Thread.new do
250
+ Puma.set_thread_name "server"
251
+ handle_servers
252
+ end
298
253
  return @thread
299
254
  else
300
255
  handle_servers
@@ -302,6 +257,7 @@ module Puma
302
257
  end
303
258
 
304
259
  def handle_servers
260
+ @check, @notify = Puma::Util.pipe unless @notify
305
261
  begin
306
262
  check = @check
307
263
  sockets = [check] + @binder.ios
@@ -326,6 +282,10 @@ module Puma
326
282
  break if handle_check
327
283
  else
328
284
  begin
285
+ pool.wait_until_not_full
286
+ pool.wait_for_less_busy_worker(
287
+ @options[:wait_for_less_busy_worker].to_f)
288
+
329
289
  if io = sock.accept_nonblock
330
290
  client = Client.new io, @binder.env(sock)
331
291
  if remote_addr_value
@@ -335,13 +295,16 @@ module Puma
335
295
  end
336
296
 
337
297
  pool << client
338
- pool.wait_until_not_full unless queue_requests
339
298
  end
340
299
  rescue SystemCallError
341
300
  # nothing
342
301
  rescue Errno::ECONNABORTED
343
302
  # client closed the socket even before accept
344
- io.close rescue nil
303
+ begin
304
+ io.close
305
+ rescue
306
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
307
+ end
345
308
  end
346
309
  end
347
310
  end
@@ -352,21 +315,20 @@ module Puma
352
315
 
353
316
  @events.fire :state, @status
354
317
 
355
- graceful_shutdown if @status == :stop || @status == :restart
356
318
  if queue_requests
357
- @reactor.clear! if @status == :restart
319
+ @queue_requests = false
320
+ @reactor.clear!
358
321
  @reactor.shutdown
359
322
  end
323
+ graceful_shutdown if @status == :stop || @status == :restart
360
324
  rescue Exception => e
361
325
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
362
326
  STDERR.puts e.backtrace
363
327
  ensure
364
- @check.close
328
+ @check.close unless @check.closed? # Ruby 2.2 issue
365
329
  @notify.close
366
-
367
- if @status != :restart and @own_binder
368
- @binder.close
369
- end
330
+ @notify = nil
331
+ @check = nil
370
332
  end
371
333
 
372
334
  @events.fire :state, :done
@@ -399,8 +361,12 @@ module Puma
399
361
  #
400
362
  def process_client(client, buffer)
401
363
  begin
364
+
365
+ clean_thread_locals = @options[:clean_thread_locals]
402
366
  close_socket = true
403
367
 
368
+ requests = 0
369
+
404
370
  while true
405
371
  case handle_request(client, buffer)
406
372
  when false
@@ -409,10 +375,24 @@ module Puma
409
375
  close_socket = false
410
376
  return
411
377
  when true
412
- return unless @queue_requests
413
378
  buffer.reset
414
379
 
415
- unless client.reset(@status == :run)
380
+ ThreadPool.clean_thread_locals if clean_thread_locals
381
+
382
+ requests += 1
383
+
384
+ check_for_more_data = @status == :run
385
+
386
+ if requests >= MAX_FAST_INLINE
387
+ # This will mean that reset will only try to use the data it already
388
+ # has buffered and won't try to read more data. What this means is that
389
+ # every client, independent of their request speed, gets treated like a slow
390
+ # one once every MAX_FAST_INLINE requests.
391
+ check_for_more_data = false
392
+ end
393
+
394
+ unless client.reset(check_for_more_data)
395
+ return unless @queue_requests
416
396
  close_socket = false
417
397
  client.set_timeout @persistent_timeout
418
398
  @reactor.add client
@@ -427,6 +407,8 @@ module Puma
427
407
 
428
408
  # SSL handshake error
429
409
  rescue MiniSSL::SSLError => e
410
+ lowlevel_error(e, client.env)
411
+
430
412
  ssl_socket = client.io
431
413
  addr = ssl_socket.peeraddr.last
432
414
  cert = ssl_socket.peercert
@@ -437,13 +419,17 @@ module Puma
437
419
 
438
420
  # The client doesn't know HTTP well
439
421
  rescue HttpParserError => e
440
- client.write_400
422
+ lowlevel_error(e, client.env)
423
+
424
+ client.write_error(400)
441
425
 
442
426
  @events.parse_error self, client.env, e
443
427
 
444
428
  # Server error
445
429
  rescue StandardError => e
446
- client.write_500
430
+ lowlevel_error(e, client.env)
431
+
432
+ client.write_error(500)
447
433
 
448
434
  @events.unknown_error self, e, "Read"
449
435
 
@@ -453,6 +439,7 @@ module Puma
453
439
  begin
454
440
  client.close if close_socket
455
441
  rescue IOError, SystemCallError
442
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
456
443
  # Already closed
457
444
  rescue StandardError => e
458
445
  @events.unknown_error self, e, "Client"
@@ -483,6 +470,10 @@ module Puma
483
470
  env[REQUEST_PATH] = uri.path
484
471
 
485
472
  raise "No REQUEST PATH" unless env[REQUEST_PATH]
473
+
474
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
475
+ # so only set the env value if there actually is a value.
476
+ env[QUERY_STRING] = uri.query if uri.query
486
477
  end
487
478
 
488
479
  env[PATH_INFO] = env[REQUEST_PATH]
@@ -513,23 +504,34 @@ module Puma
513
504
  end
514
505
 
515
506
  def default_server_port(env)
516
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
517
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
507
+ if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
508
+ PORT_443
509
+ else
510
+ PORT_80
511
+ end
518
512
  end
519
513
 
520
- # Given the request +env+ from +client+ and a partial request body
521
- # in +body+, finish reading the body if there is one and invoke
522
- # the rack app. Then construct the response and write it back to
523
- # +client+
514
+ # Takes the request +req+, invokes the Rack application to construct
515
+ # the response and writes it back to +req.io+.
516
+ #
517
+ # The second parameter +lines+ is a IO-like object unique to this thread.
518
+ # This is normally an instance of Puma::IOBuffer.
519
+ #
520
+ # It'll return +false+ when the connection is closed, this doesn't mean
521
+ # that the response wasn't successful.
524
522
  #
525
- # +cl+ is the previously fetched Content-Length header if there
526
- # was one. This is an optimization to keep from having to look
527
- # it up again.
523
+ # It'll return +:async+ if the connection remains open but will be handled
524
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
528
525
  #
526
+ # Finally, it'll return +true+ on keep-alive connections.
529
527
  def handle_request(req, lines)
528
+ @requests_count +=1
529
+
530
530
  env = req.env
531
531
  client = req.io
532
532
 
533
+ return false if closed_socket?(client)
534
+
533
535
  normalize_env env, req
534
536
 
535
537
  env[PUMA_SOCKET] = client
@@ -546,7 +548,30 @@ module Puma
546
548
  head = env[REQUEST_METHOD] == HEAD
547
549
 
548
550
  env[RACK_INPUT] = body
549
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
551
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
552
+
553
+ if @early_hints
554
+ env[EARLY_HINTS] = lambda { |headers|
555
+ begin
556
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
557
+
558
+ headers.each_pair do |k, vs|
559
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
560
+ vs.to_s.split(NEWLINE).each do |v|
561
+ next if possible_header_injection?(v)
562
+ fast_write client, "#{k}: #{v}\r\n"
563
+ end
564
+ else
565
+ fast_write client, "#{k}: #{vs}\r\n"
566
+ end
567
+ end
568
+
569
+ fast_write client, "\r\n".freeze
570
+ rescue ConnectionError
571
+ # noop, if we lost the socket we just won't send the early hints
572
+ end
573
+ }
574
+ end
550
575
 
551
576
  # A rack extension. If the app writes #call'ables to this
552
577
  # array, we will invoke them when the request is done.
@@ -568,10 +593,15 @@ module Puma
568
593
 
569
594
  return :async
570
595
  end
571
- rescue StandardError => e
572
- @events.unknown_error self, e, "Rack app"
596
+ rescue ThreadPool::ForceShutdown => e
597
+ @events.unknown_error self, e, "Rack app", env
598
+ @events.log "Detected force shutdown of a thread"
599
+
600
+ status, headers, res_body = lowlevel_error(e, env, 503)
601
+ rescue Exception => e
602
+ @events.unknown_error self, e, "Rack app", env
573
603
 
574
- status, headers, res_body = lowlevel_error(e)
604
+ status, headers, res_body = lowlevel_error(e, env, 500)
575
605
  end
576
606
 
577
607
  content_length = nil
@@ -586,10 +616,10 @@ module Puma
586
616
  line_ending = LINE_END
587
617
  colon = COLON
588
618
 
589
- http_11 = if env[HTTP_VERSION] == HTTP_11
619
+ http_11 = env[HTTP_VERSION] == HTTP_11
620
+ if http_11
590
621
  allow_chunked = true
591
622
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
592
- include_keepalive_header = false
593
623
 
594
624
  # An optimization. The most common response is 200, so we can
595
625
  # reply with the proper 200 status without having to compute
@@ -603,11 +633,9 @@ module Puma
603
633
 
604
634
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
605
635
  end
606
- true
607
636
  else
608
637
  allow_chunked = false
609
638
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
610
- include_keepalive_header = keep_alive
611
639
 
612
640
  # Same optimization as above for HTTP/1.1
613
641
  #
@@ -619,14 +647,18 @@ module Puma
619
647
 
620
648
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
621
649
  end
622
- false
623
650
  end
624
651
 
652
+ # regardless of what the client wants, we always close the connection
653
+ # if running without request queueing
654
+ keep_alive &&= @queue_requests
655
+
625
656
  response_hijack = nil
626
657
 
627
658
  headers.each do |k, vs|
628
659
  case k.downcase
629
660
  when CONTENT_LENGTH2
661
+ next if possible_header_injection?(vs)
630
662
  content_length = vs
631
663
  next
632
664
  when TRANSFER_ENCODING
@@ -637,8 +669,9 @@ module Puma
637
669
  next
638
670
  end
639
671
 
640
- if vs.respond_to?(:to_s)
672
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
641
673
  vs.to_s.split(NEWLINE).each do |v|
674
+ next if possible_header_injection?(v)
642
675
  lines.append k, colon, v, line_ending
643
676
  end
644
677
  else
@@ -646,10 +679,15 @@ module Puma
646
679
  end
647
680
  end
648
681
 
649
- if include_keepalive_header
650
- lines << CONNECTION_KEEP_ALIVE
651
- elsif http_11 && !keep_alive
652
- lines << CONNECTION_CLOSE
682
+ # HTTP/1.1 & 1.0 assume different defaults:
683
+ # - HTTP 1.0 assumes the connection will be closed if not specified
684
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
685
+ # Only set the header if we're doing something which is not the default
686
+ # for this protocol version
687
+ if http_11
688
+ lines << CONNECTION_CLOSE if !keep_alive
689
+ else
690
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
653
691
  end
654
692
 
655
693
  if no_body
@@ -681,8 +719,8 @@ module Puma
681
719
 
682
720
  begin
683
721
  res_body.each do |part|
722
+ next if part.bytesize.zero?
684
723
  if chunked
685
- next if part.bytesize.zero?
686
724
  fast_write client, part.bytesize.to_s(16)
687
725
  fast_write client, line_ending
688
726
  fast_write client, part
@@ -720,7 +758,7 @@ module Puma
720
758
  end
721
759
  private :fetch_status_code
722
760
 
723
- # Given the requset +env+ from +client+ and the partial body +body+
761
+ # Given the request +env+ from +client+ and the partial body +body+
724
762
  # plus a potential Content-Length value +cl+, finish reading
725
763
  # the body and return it.
726
764
  #
@@ -776,15 +814,21 @@ module Puma
776
814
 
777
815
  # A fallback rack response if +@app+ raises as exception.
778
816
  #
779
- def lowlevel_error(e)
817
+ def lowlevel_error(e, env, status=500)
780
818
  if handler = @options[:lowlevel_error_handler]
781
- return handler.call(e)
819
+ if handler.arity == 1
820
+ return handler.call(e)
821
+ elsif handler.arity == 2
822
+ return handler.call(e, env)
823
+ else
824
+ return handler.call(e, env, status)
825
+ end
782
826
  end
783
827
 
784
828
  if @leak_stack_on_error
785
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
829
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
786
830
  else
787
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
831
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
788
832
  end
789
833
  end
790
834
 
@@ -828,38 +872,53 @@ module Puma
828
872
  @events.debug "Drained #{count} additional connections."
829
873
  end
830
874
 
831
- @thread_pool.shutdown if @thread_pool
875
+ if @status != :restart
876
+ @binder.close
877
+ end
878
+
879
+ if @thread_pool
880
+ if timeout = @options[:force_shutdown_after]
881
+ @thread_pool.shutdown timeout.to_i
882
+ else
883
+ @thread_pool.shutdown
884
+ end
885
+ end
832
886
  end
833
887
 
834
- # Stops the acceptor thread and then causes the worker threads to finish
835
- # off the request queue before finally exiting.
836
- #
837
- def stop(sync=false)
888
+ def notify_safely(message)
889
+ @check, @notify = Puma::Util.pipe unless @notify
838
890
  begin
839
- @notify << STOP_COMMAND
891
+ @notify << message
840
892
  rescue IOError
841
- # The server, in another thread, is shutting down
893
+ # The server, in another thread, is shutting down
894
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
895
+ rescue RuntimeError => e
896
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
897
+ if e.message.include?('IOError')
898
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
899
+ else
900
+ raise e
901
+ end
842
902
  end
903
+ end
904
+ private :notify_safely
843
905
 
906
+ # Stops the acceptor thread and then causes the worker threads to finish
907
+ # off the request queue before finally exiting.
908
+
909
+ def stop(sync=false)
910
+ notify_safely(STOP_COMMAND)
844
911
  @thread.join if @thread && sync
845
912
  end
846
913
 
847
914
  def halt(sync=false)
848
- begin
849
- @notify << HALT_COMMAND
850
- rescue IOError
851
- # The server, in another thread, is shutting down
852
- end
853
-
915
+ notify_safely(HALT_COMMAND)
854
916
  @thread.join if @thread && sync
855
917
  end
856
918
 
857
- def begin_restart
858
- begin
859
- @notify << RESTART_COMMAND
860
- rescue IOError
861
- # The server, in another thread, is shutting down
862
- end
919
+ def begin_restart(sync=false)
920
+ notify_safely(RESTART_COMMAND)
921
+ @thread.join if @thread && sync
863
922
  end
864
923
 
865
924
  def fast_write(io, str)
@@ -882,5 +941,28 @@ module Puma
882
941
  end
883
942
  end
884
943
  private :fast_write
944
+
945
+ ThreadLocalKey = :puma_server
946
+
947
+ def self.current
948
+ Thread.current[ThreadLocalKey]
949
+ end
950
+
951
+ def shutting_down?
952
+ @status == :stop || @status == :restart
953
+ end
954
+
955
+ def possible_header_injection?(header_value)
956
+ HTTP_INJECTION_REGEX =~ header_value.to_s
957
+ end
958
+ private :possible_header_injection?
959
+
960
+ # List of methods invoked by #stats.
961
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
962
+
963
+ # Returns a hash of stats about the running server for reporting purposes.
964
+ def stats
965
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
966
+ end
885
967
  end
886
968
  end