puma 5.0.0.beta1-java → 5.0.3-java

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1188 -559
  3. data/README.md +15 -8
  4. data/bin/puma-wild +3 -9
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +10 -7
  7. data/docs/jungle/README.md +0 -4
  8. data/docs/jungle/rc.d/puma +2 -2
  9. data/docs/nginx.md +1 -1
  10. data/docs/restart.md +46 -23
  11. data/docs/signals.md +7 -7
  12. data/docs/systemd.md +1 -1
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/http11_parser.c +3 -1
  15. data/ext/puma_http11/http11_parser.rl +3 -1
  16. data/ext/puma_http11/mini_ssl.c +53 -38
  17. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  19. data/ext/puma_http11/puma_http11.c +22 -11
  20. data/lib/puma.rb +16 -0
  21. data/lib/puma/app/status.rb +47 -44
  22. data/lib/puma/binder.rb +40 -12
  23. data/lib/puma/client.rb +68 -82
  24. data/lib/puma/cluster.rb +30 -187
  25. data/lib/puma/cluster/worker.rb +170 -0
  26. data/lib/puma/cluster/worker_handle.rb +83 -0
  27. data/lib/puma/commonlogger.rb +2 -2
  28. data/lib/puma/configuration.rb +9 -7
  29. data/lib/puma/const.rb +2 -1
  30. data/lib/puma/control_cli.rb +2 -0
  31. data/lib/puma/detect.rb +9 -0
  32. data/lib/puma/dsl.rb +77 -39
  33. data/lib/puma/error_logger.rb +97 -0
  34. data/lib/puma/events.rb +37 -31
  35. data/lib/puma/launcher.rb +20 -10
  36. data/lib/puma/minissl.rb +55 -10
  37. data/lib/puma/minissl/context_builder.rb +0 -3
  38. data/lib/puma/puma_http11.jar +0 -0
  39. data/lib/puma/queue_close.rb +26 -0
  40. data/lib/puma/reactor.rb +77 -373
  41. data/lib/puma/request.rb +438 -0
  42. data/lib/puma/runner.rb +7 -19
  43. data/lib/puma/server.rb +229 -506
  44. data/lib/puma/single.rb +3 -2
  45. data/lib/puma/state_file.rb +1 -1
  46. data/lib/puma/thread_pool.rb +32 -5
  47. data/lib/puma/util.rb +12 -0
  48. metadata +12 -10
  49. data/docs/jungle/upstart/README.md +0 -61
  50. data/docs/jungle/upstart/puma-manager.conf +0 -31
  51. data/docs/jungle/upstart/puma.conf +0 -69
  52. data/lib/puma/accept_nonblock.rb +0 -29
@@ -9,11 +9,9 @@ require 'puma/null_io'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/accept_nonblock'
13
12
  require 'puma/util'
14
13
  require 'puma/io_buffer'
15
-
16
- require 'puma/puma_http11'
14
+ require 'puma/request'
17
15
 
18
16
  require 'socket'
19
17
  require 'forwardable'
@@ -33,19 +31,31 @@ module Puma
33
31
  class Server
34
32
 
35
33
  include Puma::Const
34
+ include Request
36
35
  extend Forwardable
37
36
 
38
37
  attr_reader :thread
39
38
  attr_reader :events
40
- attr_reader :requests_count
39
+ attr_reader :min_threads, :max_threads # for #stats
40
+ attr_reader :requests_count # @version 5.0.0
41
+
42
+ # @todo the following may be deprecated in the future
43
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
44
+ :leak_stack_on_error,
45
+ :persistent_timeout, :reaping_time
46
+
47
+ # @deprecated v6.0.0
48
+ attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
49
+ :leak_stack_on_error, :min_threads, :max_threads,
50
+ :persistent_timeout, :reaping_time
51
+
41
52
  attr_accessor :app
53
+ attr_accessor :binder
42
54
 
43
- attr_accessor :min_threads
44
- attr_accessor :max_threads
45
- attr_accessor :persistent_timeout
46
- attr_accessor :auto_trim_time
47
- attr_accessor :reaping_time
48
- attr_accessor :first_data_timeout
55
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
56
+ :add_unix_listener, :connected_ports
57
+
58
+ ThreadLocalKey = :puma_server
49
59
 
50
60
  # Create a server for the rack app +app+.
51
61
  #
@@ -55,6 +65,10 @@ module Puma
55
65
  # Server#run returns a thread that you can join on to wait for the server
56
66
  # to do its work.
57
67
  #
68
+ # @note Several instance variables exist so they are available for testing,
69
+ # and have default values set via +fetch+. Normally the values are set via
70
+ # `::Puma::Configuration.puma_default_options`.
71
+ #
58
72
  def initialize(app, events=Events.stdio, options={})
59
73
  @app = app
60
74
  @events = events
@@ -62,24 +76,25 @@ module Puma
62
76
  @check, @notify = nil
63
77
  @status = :stop
64
78
 
65
- @min_threads = 0
66
- @max_threads = 16
67
79
  @auto_trim_time = 30
68
80
  @reaping_time = 1
69
81
 
70
82
  @thread = nil
71
83
  @thread_pool = nil
72
- @early_hints = nil
73
84
 
74
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
75
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
85
+ @options = options
76
86
 
77
- @binder = Binder.new(events)
87
+ @early_hints = options.fetch :early_hints, nil
88
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
+ @min_threads = options.fetch :min_threads, 0
90
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
+ @queue_requests = options.fetch :queue_requests, true
78
93
 
79
- @leak_stack_on_error = true
94
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
95
+ @leak_stack_on_error = @options[:environment] ? temp : true
80
96
 
81
- @options = options
82
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
97
+ @binder = Binder.new(events)
83
98
 
84
99
  ENV['RACK_ENV'] ||= "development"
85
100
 
@@ -90,18 +105,39 @@ module Puma
90
105
  @requests_count = 0
91
106
  end
92
107
 
93
- attr_accessor :binder, :leak_stack_on_error, :early_hints
94
-
95
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
96
-
97
108
  def inherit_binder(bind)
98
109
  @binder = bind
99
110
  end
100
111
 
112
+ class << self
113
+ # @!attribute [r] current
114
+ def current
115
+ Thread.current[ThreadLocalKey]
116
+ end
117
+
118
+ # :nodoc:
119
+ # @version 5.0.0
120
+ def tcp_cork_supported?
121
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
122
+ Socket.const_defined?(:IPPROTO_TCP) &&
123
+ Socket.const_defined?(:TCP_CORK)
124
+ end
125
+
126
+ # :nodoc:
127
+ # @version 5.0.0
128
+ def closed_socket_supported?
129
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
130
+ Socket.const_defined?(:IPPROTO_TCP) &&
131
+ Socket.const_defined?(:TCP_INFO)
132
+ end
133
+ private :tcp_cork_supported?
134
+ private :closed_socket_supported?
135
+ end
136
+
101
137
  # On Linux, use TCP_CORK to better control how the TCP stack
102
138
  # packetizes our stream. This improves both latency and throughput.
103
139
  #
104
- if RUBY_PLATFORM =~ /linux/
140
+ if tcp_cork_supported?
105
141
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
106
142
 
107
143
  # 6 == Socket::IPPROTO_TCP
@@ -109,7 +145,7 @@ module Puma
109
145
  # 1/0 == turn on/off
110
146
  def cork_socket(socket)
111
147
  begin
112
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
148
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
113
149
  rescue IOError, SystemCallError
114
150
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
115
151
  end
@@ -117,18 +153,26 @@ module Puma
117
153
 
118
154
  def uncork_socket(socket)
119
155
  begin
120
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
156
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
121
157
  rescue IOError, SystemCallError
122
158
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
123
159
  end
124
160
  end
161
+ else
162
+ def cork_socket(socket)
163
+ end
125
164
 
165
+ def uncork_socket(socket)
166
+ end
167
+ end
168
+
169
+ if closed_socket_supported?
126
170
  def closed_socket?(socket)
127
171
  return false unless socket.kind_of? TCPSocket
128
172
  return false unless @precheck_closing
129
173
 
130
174
  begin
131
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
175
+ tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
132
176
  rescue IOError, SystemCallError
133
177
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
134
178
  @precheck_closing = false
@@ -140,21 +184,17 @@ module Puma
140
184
  end
141
185
  end
142
186
  else
143
- def cork_socket(socket)
144
- end
145
-
146
- def uncork_socket(socket)
147
- end
148
-
149
187
  def closed_socket?(socket)
150
188
  false
151
189
  end
152
190
  end
153
191
 
192
+ # @!attribute [r] backlog
154
193
  def backlog
155
194
  @thread_pool and @thread_pool.backlog
156
195
  end
157
196
 
197
+ # @!attribute [r] running
158
198
  def running
159
199
  @thread_pool and @thread_pool.spawned
160
200
  end
@@ -167,6 +207,7 @@ module Puma
167
207
  # there are 5 threads sitting idle ready to take
168
208
  # a request. If one request comes in, then the
169
209
  # value would be 4 until it finishes processing.
210
+ # @!attribute [r] pool_capacity
170
211
  def pool_capacity
171
212
  @thread_pool and @thread_pool.pool_capacity
172
213
  end
@@ -184,55 +225,19 @@ module Puma
184
225
 
185
226
  @status = :run
186
227
 
187
- @thread_pool = ThreadPool.new(@min_threads,
188
- @max_threads,
189
- ::Puma::IOBuffer) do |client, buffer|
190
-
191
- # Advertise this server into the thread
192
- Thread.current[ThreadLocalKey] = self
193
-
194
- process_now = false
195
-
196
- begin
197
- if @queue_requests
198
- process_now = client.eagerly_finish
199
- else
200
- client.finish(@first_data_timeout)
201
- process_now = true
202
- end
203
- rescue MiniSSL::SSLError => e
204
- ssl_socket = client.io
205
- addr = ssl_socket.peeraddr.last
206
- cert = ssl_socket.peercert
207
-
208
- client.close
209
-
210
- @events.ssl_error self, addr, cert, e
211
- rescue HttpParserError => e
212
- client.write_error(400)
213
- client.close
214
-
215
- @events.parse_error self, client.env, e
216
- rescue ConnectionError, EOFError
217
- client.close
218
- else
219
- if process_now
220
- process_client client, buffer
221
- else
222
- client.set_timeout @first_data_timeout
223
- @reactor.add client
224
- end
225
- end
226
-
227
- process_now
228
- end
228
+ @thread_pool = ThreadPool.new(
229
+ @min_threads,
230
+ @max_threads,
231
+ ::Puma::IOBuffer,
232
+ &method(:process_client)
233
+ )
229
234
 
230
235
  @thread_pool.out_of_band_hook = @options[:out_of_band]
231
236
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
232
237
 
233
238
  if @queue_requests
234
- @reactor = Reactor.new self, @thread_pool
235
- @reactor.run_in_thread
239
+ @reactor = Reactor.new(&method(:reactor_wakeup))
240
+ @reactor.run
236
241
  end
237
242
 
238
243
  if @reaping_time
@@ -243,6 +248,8 @@ module Puma
243
248
  @thread_pool.auto_trim!(@auto_trim_time)
244
249
  end
245
250
 
251
+ @check, @notify = Puma::Util.pipe unless @notify
252
+
246
253
  @events.fire :state, :running
247
254
 
248
255
  if background
@@ -256,8 +263,45 @@ module Puma
256
263
  end
257
264
  end
258
265
 
266
+ # This method is called from the Reactor thread when a queued Client receives data,
267
+ # times out, or when the Reactor is shutting down.
268
+ #
269
+ # It is responsible for ensuring that a request has been completely received
270
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
271
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
272
+ # such as nginx) then the application would be subject to a slow client attack.
273
+ #
274
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
275
+ #
276
+ # The method checks to see if it has the full header and body with
277
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
278
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
279
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
280
+ # The Client is then removed from the reactor (return `true`).
281
+ #
282
+ # If a client object times out, a 408 response is written, its connection is closed,
283
+ # and the object is removed from the reactor (return `true`).
284
+ #
285
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
286
+ # ThreadPool, depending on their current state (#can_close?).
287
+ #
288
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
289
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
290
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
291
+ def reactor_wakeup(client)
292
+ shutdown = !@queue_requests
293
+ if client.try_to_finish || (shutdown && !client.can_close?)
294
+ @thread_pool << client
295
+ elsif shutdown || client.timeout == 0
296
+ client.timeout!
297
+ end
298
+ rescue StandardError => e
299
+ client_error(e, client)
300
+ client.close
301
+ true
302
+ end
303
+
259
304
  def handle_servers
260
- @check, @notify = Puma::Util.pipe unless @notify
261
305
  begin
262
306
  check = @check
263
307
  sockets = [check] + @binder.ios
@@ -281,35 +325,26 @@ module Puma
281
325
  if sock == check
282
326
  break if handle_check
283
327
  else
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
-
289
- if io = sock.accept_nonblock
290
- client = Client.new io, @binder.env(sock)
291
- if remote_addr_value
292
- client.peerip = remote_addr_value
293
- elsif remote_addr_header
294
- client.remote_addr_header = remote_addr_header
295
- end
296
-
297
- pool << client
298
- end
299
- rescue SystemCallError
300
- # nothing
301
- rescue Errno::ECONNABORTED
302
- # client closed the socket even before accept
303
- begin
304
- io.close
305
- rescue
306
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
307
- end
328
+ pool.wait_until_not_full
329
+ pool.wait_for_less_busy_worker(
330
+ @options[:wait_for_less_busy_worker].to_f)
331
+
332
+ io = begin
333
+ sock.accept_nonblock
334
+ rescue IO::WaitReadable
335
+ next
336
+ end
337
+ client = Client.new io, @binder.env(sock)
338
+ if remote_addr_value
339
+ client.peerip = remote_addr_value
340
+ elsif remote_addr_header
341
+ client.remote_addr_header = remote_addr_header
308
342
  end
343
+ pool << client
309
344
  end
310
345
  end
311
346
  rescue Object => e
312
- @events.unknown_error self, e, "Listen loop"
347
+ @events.unknown_error e, nil, "Listen loop"
313
348
  end
314
349
  end
315
350
 
@@ -317,15 +352,18 @@ module Puma
317
352
 
318
353
  if queue_requests
319
354
  @queue_requests = false
320
- @reactor.clear!
321
355
  @reactor.shutdown
322
356
  end
323
357
  graceful_shutdown if @status == :stop || @status == :restart
324
358
  rescue Exception => e
325
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
326
- STDERR.puts e.backtrace
359
+ @events.unknown_error e, nil, "Exception handling servers"
327
360
  ensure
328
- @check.close unless @check.closed? # Ruby 2.2 issue
361
+ begin
362
+ @check.close unless @check.closed?
363
+ rescue Errno::EBADF, RuntimeError
364
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
365
+ # Errno::EBADF is infrequently raised
366
+ end
329
367
  @notify.close
330
368
  @notify = nil
331
369
  @check = nil
@@ -353,27 +391,48 @@ module Puma
353
391
  return false
354
392
  end
355
393
 
356
- # Given a connection on +client+, handle the incoming requests.
394
+ # Given a connection on +client+, handle the incoming requests,
395
+ # or queue the connection in the Reactor if no request is available.
396
+ #
397
+ # This method is called from a ThreadPool worker thread.
357
398
  #
358
- # This method support HTTP Keep-Alive so it may, depending on if the client
399
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
359
400
  # indicates that it supports keep alive, wait for another request before
360
401
  # returning.
361
402
  #
403
+ # Return true if one or more requests were processed.
362
404
  def process_client(client, buffer)
405
+ # Advertise this server into the thread
406
+ Thread.current[ThreadLocalKey] = self
407
+
408
+ clean_thread_locals = @options[:clean_thread_locals]
409
+ close_socket = true
410
+
411
+ requests = 0
412
+
363
413
  begin
414
+ if @queue_requests &&
415
+ !client.eagerly_finish
364
416
 
365
- clean_thread_locals = @options[:clean_thread_locals]
366
- close_socket = true
417
+ client.set_timeout(@first_data_timeout)
418
+ if @reactor.add client
419
+ close_socket = false
420
+ return false
421
+ end
422
+ end
367
423
 
368
- requests = 0
424
+ with_force_shutdown(client) do
425
+ client.finish(@first_data_timeout)
426
+ end
369
427
 
370
428
  while true
429
+ @requests_count += 1
371
430
  case handle_request(client, buffer)
372
431
  when false
373
- return
432
+ break
374
433
  when :async
375
434
  close_socket = false
376
- return
435
+ break
377
436
  when true
378
437
  buffer.reset
379
438
 
@@ -391,48 +450,25 @@ module Puma
391
450
  check_for_more_data = false
392
451
  end
393
452
 
394
- unless client.reset(check_for_more_data)
395
- return unless @queue_requests
396
- close_socket = false
453
+ next_request_ready = with_force_shutdown(client) do
454
+ client.reset(check_for_more_data)
455
+ end
456
+
457
+ unless next_request_ready
458
+ break unless @queue_requests
397
459
  client.set_timeout @persistent_timeout
398
- @reactor.add client
399
- return
460
+ if @reactor.add client
461
+ close_socket = false
462
+ break
463
+ end
400
464
  end
401
465
  end
402
466
  end
403
-
404
- # The client disconnected while we were reading data
405
- rescue ConnectionError
406
- # Swallow them. The ensure tries to close +client+ down
407
-
408
- # SSL handshake error
409
- rescue MiniSSL::SSLError => e
410
- lowlevel_error(e, client.env)
411
-
412
- ssl_socket = client.io
413
- addr = ssl_socket.peeraddr.last
414
- cert = ssl_socket.peercert
415
-
416
- close_socket = true
417
-
418
- @events.ssl_error self, addr, cert, e
419
-
420
- # The client doesn't know HTTP well
421
- rescue HttpParserError => e
422
- lowlevel_error(e, client.env)
423
-
424
- client.write_error(400)
425
-
426
- @events.parse_error self, client.env, e
427
-
428
- # Server error
467
+ true
429
468
  rescue StandardError => e
430
- lowlevel_error(e, client.env)
431
-
432
- client.write_error(500)
433
-
434
- @events.unknown_error self, e, "Read"
435
-
469
+ client_error(e, client)
470
+ # The ensure tries to close +client+ down
471
+ requests > 0
436
472
  ensure
437
473
  buffer.reset
438
474
 
@@ -442,321 +478,20 @@ module Puma
442
478
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
443
479
  # Already closed
444
480
  rescue StandardError => e
445
- @events.unknown_error self, e, "Client"
481
+ @events.unknown_error e, nil, "Client"
446
482
  end
447
483
  end
448
484
  end
449
485
 
450
- # Given a Hash +env+ for the request read from +client+, add
451
- # and fixup keys to comply with Rack's env guidelines.
452
- #
453
- def normalize_env(env, client)
454
- if host = env[HTTP_HOST]
455
- if colon = host.index(":")
456
- env[SERVER_NAME] = host[0, colon]
457
- env[SERVER_PORT] = host[colon+1, host.bytesize]
458
- else
459
- env[SERVER_NAME] = host
460
- env[SERVER_PORT] = default_server_port(env)
461
- end
462
- else
463
- env[SERVER_NAME] = LOCALHOST
464
- env[SERVER_PORT] = default_server_port(env)
465
- end
466
-
467
- unless env[REQUEST_PATH]
468
- # it might be a dumbass full host request header
469
- uri = URI.parse(env[REQUEST_URI])
470
- env[REQUEST_PATH] = uri.path
471
-
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
477
- end
478
-
479
- env[PATH_INFO] = env[REQUEST_PATH]
480
-
481
- # From http://www.ietf.org/rfc/rfc3875 :
482
- # "Script authors should be aware that the REMOTE_ADDR and
483
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
484
- # may not identify the ultimate source of the request.
485
- # They identify the client for the immediate request to the
486
- # server; that client may be a proxy, gateway, or other
487
- # intermediary acting on behalf of the actual source client."
488
- #
489
-
490
- unless env.key?(REMOTE_ADDR)
491
- begin
492
- addr = client.peerip
493
- rescue Errno::ENOTCONN
494
- # Client disconnects can result in an inability to get the
495
- # peeraddr from the socket; default to localhost.
496
- addr = LOCALHOST_IP
497
- end
498
-
499
- # Set unix socket addrs to localhost
500
- addr = LOCALHOST_IP if addr.empty?
501
-
502
- env[REMOTE_ADDR] = addr
503
- end
486
+ # Triggers a client timeout if the thread-pool shuts down
487
+ # during execution of the provided block.
488
+ def with_force_shutdown(client, &block)
489
+ @thread_pool.with_force_shutdown(&block)
490
+ rescue ThreadPool::ForceShutdown
491
+ client.timeout!
504
492
  end
505
493
 
506
- def default_server_port(env)
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
512
- end
513
-
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.
522
- #
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.
525
- #
526
- # Finally, it'll return +true+ on keep-alive connections.
527
- def handle_request(req, lines)
528
- @requests_count +=1
529
-
530
- env = req.env
531
- client = req.io
532
-
533
- return false if closed_socket?(client)
534
-
535
- normalize_env env, req
536
-
537
- env[PUMA_SOCKET] = client
538
-
539
- if env[HTTPS_KEY] && client.peercert
540
- env[PUMA_PEERCERT] = client.peercert
541
- end
542
-
543
- env[HIJACK_P] = true
544
- env[HIJACK] = req
545
-
546
- body = req.body
547
-
548
- head = env[REQUEST_METHOD] == HEAD
549
-
550
- env[RACK_INPUT] = body
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
575
-
576
- # A rack extension. If the app writes #call'ables to this
577
- # array, we will invoke them when the request is done.
578
- #
579
- after_reply = env[RACK_AFTER_REPLY] = []
580
-
581
- begin
582
- begin
583
- status, headers, res_body = @app.call(env)
584
-
585
- return :async if req.hijacked
586
-
587
- status = status.to_i
588
-
589
- if status == -1
590
- unless headers.empty? and res_body == []
591
- raise "async response must have empty headers and body"
592
- end
593
-
594
- return :async
595
- end
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
603
-
604
- status, headers, res_body = lowlevel_error(e, env, 500)
605
- end
606
-
607
- content_length = nil
608
- no_body = head
609
-
610
- if res_body.kind_of? Array and res_body.size == 1
611
- content_length = res_body[0].bytesize
612
- end
613
-
614
- cork_socket client
615
-
616
- line_ending = LINE_END
617
- colon = COLON
618
-
619
- http_11 = env[HTTP_VERSION] == HTTP_11
620
- if http_11
621
- allow_chunked = true
622
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
623
-
624
- # An optimization. The most common response is 200, so we can
625
- # reply with the proper 200 status without having to compute
626
- # the response header.
627
- #
628
- if status == 200
629
- lines << HTTP_11_200
630
- else
631
- lines.append "HTTP/1.1 ", status.to_s, " ",
632
- fetch_status_code(status), line_ending
633
-
634
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
635
- end
636
- else
637
- allow_chunked = false
638
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
639
-
640
- # Same optimization as above for HTTP/1.1
641
- #
642
- if status == 200
643
- lines << HTTP_10_200
644
- else
645
- lines.append "HTTP/1.0 ", status.to_s, " ",
646
- fetch_status_code(status), line_ending
647
-
648
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
649
- end
650
- end
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
-
656
- response_hijack = nil
657
-
658
- headers.each do |k, vs|
659
- case k.downcase
660
- when CONTENT_LENGTH2
661
- next if possible_header_injection?(vs)
662
- content_length = vs
663
- next
664
- when TRANSFER_ENCODING
665
- allow_chunked = false
666
- content_length = nil
667
- when HIJACK
668
- response_hijack = vs
669
- next
670
- end
671
-
672
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
673
- vs.to_s.split(NEWLINE).each do |v|
674
- next if possible_header_injection?(v)
675
- lines.append k, colon, v, line_ending
676
- end
677
- else
678
- lines.append k, colon, line_ending
679
- end
680
- end
681
-
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
691
- end
692
-
693
- if no_body
694
- if content_length and status != 204
695
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
696
- end
697
-
698
- lines << line_ending
699
- fast_write client, lines.to_s
700
- return keep_alive
701
- end
702
-
703
- if content_length
704
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
705
- chunked = false
706
- elsif !response_hijack and allow_chunked
707
- lines << TRANSFER_ENCODING_CHUNKED
708
- chunked = true
709
- end
710
-
711
- lines << line_ending
712
-
713
- fast_write client, lines.to_s
714
-
715
- if response_hijack
716
- response_hijack.call client
717
- return :async
718
- end
719
-
720
- begin
721
- res_body.each do |part|
722
- next if part.bytesize.zero?
723
- if chunked
724
- fast_write client, part.bytesize.to_s(16)
725
- fast_write client, line_ending
726
- fast_write client, part
727
- fast_write client, line_ending
728
- else
729
- fast_write client, part
730
- end
731
-
732
- client.flush
733
- end
734
-
735
- if chunked
736
- fast_write client, CLOSE_CHUNKED
737
- client.flush
738
- end
739
- rescue SystemCallError, IOError
740
- raise ConnectionError, "Connection error detected during write"
741
- end
742
-
743
- ensure
744
- uncork_socket client
745
-
746
- body.close
747
- req.tempfile.unlink if req.tempfile
748
- res_body.close if res_body.respond_to? :close
749
-
750
- after_reply.each { |o| o.call }
751
- end
752
-
753
- return keep_alive
754
- end
755
-
756
- def fetch_status_code(status)
757
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
758
- end
759
- private :fetch_status_code
494
+ # :nocov:
760
495
 
761
496
  # Given the request +env+ from +client+ and the partial body +body+
762
497
  # plus a potential Content-Length value +cl+, finish reading
@@ -764,6 +499,7 @@ module Puma
764
499
  #
765
500
  # If the body is larger than MAX_BODY, a Tempfile object is used
766
501
  # for the body, otherwise a StringIO is used.
502
+ # @deprecated 6.0.0
767
503
  #
768
504
  def read_body(env, client, body, cl)
769
505
  content_length = cl.to_i
@@ -796,7 +532,7 @@ module Puma
796
532
 
797
533
  remain -= stream.write(chunk)
798
534
 
799
- # Raed the rest of the chunks
535
+ # Read the rest of the chunks
800
536
  while remain > 0
801
537
  chunk = client.readpartial(CHUNK_SIZE)
802
538
  unless chunk
@@ -811,6 +547,25 @@ module Puma
811
547
 
812
548
  return stream
813
549
  end
550
+ # :nocov:
551
+
552
+ # Handle various error types thrown by Client I/O operations.
553
+ def client_error(e, client)
554
+ # Swallow, do not log
555
+ return if [ConnectionError, EOFError].include?(e.class)
556
+
557
+ lowlevel_error(e, client.env)
558
+ case e
559
+ when MiniSSL::SSLError
560
+ @events.ssl_error e, client.io
561
+ when HttpParserError
562
+ client.write_error(400)
563
+ @events.parse_error e, client
564
+ else
565
+ client.write_error(500)
566
+ @events.unknown_error e, nil, "Read"
567
+ end
568
+ end
814
569
 
815
570
  # A fallback rack response if +@app+ raises as exception.
816
571
  #
@@ -878,7 +633,7 @@ module Puma
878
633
 
879
634
  if @thread_pool
880
635
  if timeout = @options[:force_shutdown_after]
881
- @thread_pool.shutdown timeout.to_i
636
+ @thread_pool.shutdown timeout.to_f
882
637
  else
883
638
  @thread_pool.shutdown
884
639
  end
@@ -886,19 +641,16 @@ module Puma
886
641
  end
887
642
 
888
643
  def notify_safely(message)
889
- @check, @notify = Puma::Util.pipe unless @notify
890
- begin
891
- @notify << message
892
- rescue IOError
893
- # The server, in another thread, is shutting down
644
+ @notify << message
645
+ rescue IOError, NoMethodError, Errno::EPIPE
646
+ # The server, in another thread, is shutting down
647
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
648
+ rescue RuntimeError => e
649
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
650
+ if e.message.include?('IOError')
894
651
  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
652
+ else
653
+ raise e
902
654
  end
903
655
  end
904
656
  private :notify_safely
@@ -921,46 +673,17 @@ module Puma
921
673
  @thread.join if @thread && sync
922
674
  end
923
675
 
924
- def fast_write(io, str)
925
- n = 0
926
- while true
927
- begin
928
- n = io.syswrite str
929
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
930
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
931
- raise ConnectionError, "Socket timeout writing data"
932
- end
933
-
934
- retry
935
- rescue Errno::EPIPE, SystemCallError, IOError
936
- raise ConnectionError, "Socket timeout writing data"
937
- end
938
-
939
- return if n == str.bytesize
940
- str = str.byteslice(n..-1)
941
- end
942
- end
943
- private :fast_write
944
-
945
- ThreadLocalKey = :puma_server
946
-
947
- def self.current
948
- Thread.current[ThreadLocalKey]
949
- end
950
-
951
676
  def shutting_down?
952
677
  @status == :stop || @status == :restart
953
678
  end
954
679
 
955
- def possible_header_injection?(header_value)
956
- HTTP_INJECTION_REGEX =~ header_value.to_s
957
- end
958
- private :possible_header_injection?
959
-
960
680
  # List of methods invoked by #stats.
681
+ # @version 5.0.0
961
682
  STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
962
683
 
963
684
  # Returns a hash of stats about the running server for reporting purposes.
685
+ # @version 5.0.0
686
+ # @!attribute [r] stats
964
687
  def stats
965
688
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
966
689
  end