puma 5.0.0-java → 5.1.0-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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1190 -574
  3. data/README.md +28 -20
  4. data/bin/puma-wild +3 -9
  5. data/docs/compile_options.md +19 -0
  6. data/docs/deployment.md +5 -6
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/jungle/README.md +0 -4
  9. data/docs/jungle/rc.d/puma +2 -2
  10. data/docs/nginx.md +1 -1
  11. data/docs/restart.md +46 -23
  12. data/docs/systemd.md +25 -3
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/extconf.rb +4 -5
  15. data/ext/puma_http11/http11_parser.c +64 -64
  16. data/ext/puma_http11/mini_ssl.c +39 -37
  17. data/ext/puma_http11/puma_http11.c +25 -12
  18. data/lib/puma.rb +7 -4
  19. data/lib/puma/app/status.rb +44 -46
  20. data/lib/puma/binder.rb +48 -1
  21. data/lib/puma/cli.rb +4 -0
  22. data/lib/puma/client.rb +31 -80
  23. data/lib/puma/cluster.rb +39 -202
  24. data/lib/puma/cluster/worker.rb +176 -0
  25. data/lib/puma/cluster/worker_handle.rb +86 -0
  26. data/lib/puma/configuration.rb +20 -8
  27. data/lib/puma/const.rb +11 -3
  28. data/lib/puma/control_cli.rb +71 -70
  29. data/lib/puma/dsl.rb +67 -19
  30. data/lib/puma/error_logger.rb +2 -2
  31. data/lib/puma/events.rb +21 -3
  32. data/lib/puma/json.rb +96 -0
  33. data/lib/puma/launcher.rb +61 -12
  34. data/lib/puma/minissl.rb +8 -0
  35. data/lib/puma/puma_http11.jar +0 -0
  36. data/lib/puma/queue_close.rb +26 -0
  37. data/lib/puma/reactor.rb +79 -373
  38. data/lib/puma/request.rb +451 -0
  39. data/lib/puma/runner.rb +15 -21
  40. data/lib/puma/server.rb +193 -508
  41. data/lib/puma/single.rb +3 -2
  42. data/lib/puma/state_file.rb +5 -3
  43. data/lib/puma/systemd.rb +46 -0
  44. data/lib/puma/thread_pool.rb +22 -2
  45. data/lib/puma/util.rb +12 -0
  46. metadata +9 -6
  47. data/docs/jungle/upstart/README.md +0 -61
  48. data/docs/jungle/upstart/puma-manager.conf +0 -31
  49. data/docs/jungle/upstart/puma.conf +0 -69
  50. data/lib/puma/accept_nonblock.rb +0 -29
@@ -11,6 +11,7 @@ require 'puma/client'
11
11
  require 'puma/binder'
12
12
  require 'puma/util'
13
13
  require 'puma/io_buffer'
14
+ require 'puma/request'
14
15
 
15
16
  require 'socket'
16
17
  require 'forwardable'
@@ -30,19 +31,31 @@ module Puma
30
31
  class Server
31
32
 
32
33
  include Puma::Const
34
+ include Request
33
35
  extend Forwardable
34
36
 
35
37
  attr_reader :thread
36
38
  attr_reader :events
37
- attr_reader :requests_count # @version 5.0.0
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
+
38
52
  attr_accessor :app
53
+ attr_accessor :binder
54
+
55
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
56
+ :add_unix_listener, :connected_ports
39
57
 
40
- attr_accessor :min_threads
41
- attr_accessor :max_threads
42
- attr_accessor :persistent_timeout
43
- attr_accessor :auto_trim_time
44
- attr_accessor :reaping_time
45
- attr_accessor :first_data_timeout
58
+ ThreadLocalKey = :puma_server
46
59
 
47
60
  # Create a server for the rack app +app+.
48
61
  #
@@ -52,6 +65,10 @@ module Puma
52
65
  # Server#run returns a thread that you can join on to wait for the server
53
66
  # to do its work.
54
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
+ #
55
72
  def initialize(app, events=Events.stdio, options={})
56
73
  @app = app
57
74
  @events = events
@@ -59,24 +76,26 @@ module Puma
59
76
  @check, @notify = nil
60
77
  @status = :stop
61
78
 
62
- @min_threads = 0
63
- @max_threads = 16
64
79
  @auto_trim_time = 30
65
80
  @reaping_time = 1
66
81
 
67
82
  @thread = nil
68
83
  @thread_pool = nil
69
- @early_hints = nil
70
84
 
71
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
72
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
85
+ @options = options
73
86
 
74
- @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
93
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
75
94
 
76
- @leak_stack_on_error = true
95
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
96
+ @leak_stack_on_error = @options[:environment] ? temp : true
77
97
 
78
- @options = options
79
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
98
+ @binder = Binder.new(events)
80
99
 
81
100
  ENV['RACK_ENV'] ||= "development"
82
101
 
@@ -87,24 +106,33 @@ module Puma
87
106
  @requests_count = 0
88
107
  end
89
108
 
90
- attr_accessor :binder, :leak_stack_on_error, :early_hints
91
-
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
-
94
109
  def inherit_binder(bind)
95
110
  @binder = bind
96
111
  end
97
112
 
98
113
  class << self
114
+ # @!attribute [r] current
115
+ def current
116
+ Thread.current[ThreadLocalKey]
117
+ end
118
+
99
119
  # :nodoc:
100
120
  # @version 5.0.0
101
121
  def tcp_cork_supported?
102
122
  RbConfig::CONFIG['host_os'] =~ /linux/ &&
103
123
  Socket.const_defined?(:IPPROTO_TCP) &&
104
- Socket.const_defined?(:TCP_CORK) &&
124
+ Socket.const_defined?(:TCP_CORK)
125
+ end
126
+
127
+ # :nodoc:
128
+ # @version 5.0.0
129
+ def closed_socket_supported?
130
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
131
+ Socket.const_defined?(:IPPROTO_TCP) &&
105
132
  Socket.const_defined?(:TCP_INFO)
106
133
  end
107
134
  private :tcp_cork_supported?
135
+ private :closed_socket_supported?
108
136
  end
109
137
 
110
138
  # On Linux, use TCP_CORK to better control how the TCP stack
@@ -131,7 +159,15 @@ module Puma
131
159
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
132
160
  end
133
161
  end
162
+ else
163
+ def cork_socket(socket)
164
+ end
134
165
 
166
+ def uncork_socket(socket)
167
+ end
168
+ end
169
+
170
+ if closed_socket_supported?
135
171
  def closed_socket?(socket)
136
172
  return false unless socket.kind_of? TCPSocket
137
173
  return false unless @precheck_closing
@@ -149,21 +185,17 @@ module Puma
149
185
  end
150
186
  end
151
187
  else
152
- def cork_socket(socket)
153
- end
154
-
155
- def uncork_socket(socket)
156
- end
157
-
158
188
  def closed_socket?(socket)
159
189
  false
160
190
  end
161
191
  end
162
192
 
193
+ # @!attribute [r] backlog
163
194
  def backlog
164
195
  @thread_pool and @thread_pool.backlog
165
196
  end
166
197
 
198
+ # @!attribute [r] running
167
199
  def running
168
200
  @thread_pool and @thread_pool.spawned
169
201
  end
@@ -176,6 +208,7 @@ module Puma
176
208
  # there are 5 threads sitting idle ready to take
177
209
  # a request. If one request comes in, then the
178
210
  # value would be 4 until it finishes processing.
211
+ # @!attribute [r] pool_capacity
179
212
  def pool_capacity
180
213
  @thread_pool and @thread_pool.pool_capacity
181
214
  end
@@ -186,64 +219,26 @@ module Puma
186
219
  # up in the background to handle requests. Otherwise requests
187
220
  # are handled synchronously.
188
221
  #
189
- def run(background=true)
222
+ def run(background=true, thread_name: 'server')
190
223
  BasicSocket.do_not_reverse_lookup = true
191
224
 
192
225
  @events.fire :state, :booting
193
226
 
194
227
  @status = :run
195
228
 
196
- @thread_pool = ThreadPool.new(@min_threads,
197
- @max_threads,
198
- ::Puma::IOBuffer) do |client, buffer|
199
-
200
- # Advertise this server into the thread
201
- Thread.current[ThreadLocalKey] = self
202
-
203
- process_now = false
204
-
205
- begin
206
- if @queue_requests
207
- process_now = client.eagerly_finish
208
- else
209
- client.finish(@first_data_timeout)
210
- process_now = true
211
- end
212
- rescue MiniSSL::SSLError => e
213
- ssl_socket = client.io
214
- addr = ssl_socket.peeraddr.last
215
- cert = ssl_socket.peercert
216
-
217
- client.close
218
-
219
- @events.ssl_error e, addr, cert
220
- rescue HttpParserError => e
221
- client.write_error(400)
222
- client.close
223
-
224
- @events.parse_error e, client
225
- rescue ConnectionError, EOFError => e
226
- client.close
227
-
228
- @events.connection_error e, client
229
- else
230
- if process_now
231
- process_client client, buffer
232
- else
233
- client.set_timeout @first_data_timeout
234
- @reactor.add client
235
- end
236
- end
237
-
238
- process_now
239
- end
229
+ @thread_pool = ThreadPool.new(
230
+ @min_threads,
231
+ @max_threads,
232
+ ::Puma::IOBuffer,
233
+ &method(:process_client)
234
+ )
240
235
 
241
236
  @thread_pool.out_of_band_hook = @options[:out_of_band]
242
237
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
243
238
 
244
239
  if @queue_requests
245
- @reactor = Reactor.new self, @thread_pool
246
- @reactor.run_in_thread
240
+ @reactor = Reactor.new(&method(:reactor_wakeup))
241
+ @reactor.run
247
242
  end
248
243
 
249
244
  if @reaping_time
@@ -254,11 +249,13 @@ module Puma
254
249
  @thread_pool.auto_trim!(@auto_trim_time)
255
250
  end
256
251
 
252
+ @check, @notify = Puma::Util.pipe unless @notify
253
+
257
254
  @events.fire :state, :running
258
255
 
259
256
  if background
260
257
  @thread = Thread.new do
261
- Puma.set_thread_name "server"
258
+ Puma.set_thread_name thread_name
262
259
  handle_servers
263
260
  end
264
261
  return @thread
@@ -267,8 +264,45 @@ module Puma
267
264
  end
268
265
  end
269
266
 
267
+ # This method is called from the Reactor thread when a queued Client receives data,
268
+ # times out, or when the Reactor is shutting down.
269
+ #
270
+ # It is responsible for ensuring that a request has been completely received
271
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
272
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
273
+ # such as nginx) then the application would be subject to a slow client attack.
274
+ #
275
+ # 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).
276
+ #
277
+ # The method checks to see if it has the full header and body with
278
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
279
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
280
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
281
+ # The Client is then removed from the reactor (return `true`).
282
+ #
283
+ # If a client object times out, a 408 response is written, its connection is closed,
284
+ # and the object is removed from the reactor (return `true`).
285
+ #
286
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
287
+ # ThreadPool, depending on their current state (#can_close?).
288
+ #
289
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
290
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
291
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
292
+ def reactor_wakeup(client)
293
+ shutdown = !@queue_requests
294
+ if client.try_to_finish || (shutdown && !client.can_close?)
295
+ @thread_pool << client
296
+ elsif shutdown || client.timeout == 0
297
+ client.timeout!
298
+ end
299
+ rescue StandardError => e
300
+ client_error(e, client)
301
+ client.close
302
+ true
303
+ end
304
+
270
305
  def handle_servers
271
- @check, @notify = Puma::Util.pipe unless @notify
272
306
  begin
273
307
  check = @check
274
308
  sockets = [check] + @binder.ios
@@ -319,7 +353,6 @@ module Puma
319
353
 
320
354
  if queue_requests
321
355
  @queue_requests = false
322
- @reactor.clear!
323
356
  @reactor.shutdown
324
357
  end
325
358
  graceful_shutdown if @status == :stop || @status == :restart
@@ -359,27 +392,48 @@ module Puma
359
392
  return false
360
393
  end
361
394
 
362
- # Given a connection on +client+, handle the incoming requests.
395
+ # Given a connection on +client+, handle the incoming requests,
396
+ # or queue the connection in the Reactor if no request is available.
397
+ #
398
+ # This method is called from a ThreadPool worker thread.
363
399
  #
364
- # This method support HTTP Keep-Alive so it may, depending on if the client
400
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
365
401
  # indicates that it supports keep alive, wait for another request before
366
402
  # returning.
367
403
  #
404
+ # Return true if one or more requests were processed.
368
405
  def process_client(client, buffer)
406
+ # Advertise this server into the thread
407
+ Thread.current[ThreadLocalKey] = self
408
+
409
+ clean_thread_locals = @options[:clean_thread_locals]
410
+ close_socket = true
411
+
412
+ requests = 0
413
+
369
414
  begin
415
+ if @queue_requests &&
416
+ !client.eagerly_finish
370
417
 
371
- clean_thread_locals = @options[:clean_thread_locals]
372
- close_socket = true
418
+ client.set_timeout(@first_data_timeout)
419
+ if @reactor.add client
420
+ close_socket = false
421
+ return false
422
+ end
423
+ end
373
424
 
374
- requests = 0
425
+ with_force_shutdown(client) do
426
+ client.finish(@first_data_timeout)
427
+ end
375
428
 
376
429
  while true
430
+ @requests_count += 1
377
431
  case handle_request(client, buffer)
378
432
  when false
379
- return
433
+ break
380
434
  when :async
381
435
  close_socket = false
382
- return
436
+ break
383
437
  when true
384
438
  buffer.reset
385
439
 
@@ -389,55 +443,33 @@ module Puma
389
443
 
390
444
  check_for_more_data = @status == :run
391
445
 
392
- if requests >= MAX_FAST_INLINE
446
+ if requests >= @max_fast_inline
393
447
  # This will mean that reset will only try to use the data it already
394
448
  # has buffered and won't try to read more data. What this means is that
395
449
  # every client, independent of their request speed, gets treated like a slow
396
- # one once every MAX_FAST_INLINE requests.
450
+ # one once every max_fast_inline requests.
397
451
  check_for_more_data = false
398
452
  end
399
453
 
400
- unless client.reset(check_for_more_data)
401
- return unless @queue_requests
402
- close_socket = false
454
+ next_request_ready = with_force_shutdown(client) do
455
+ client.reset(check_for_more_data)
456
+ end
457
+
458
+ unless next_request_ready
459
+ break unless @queue_requests
403
460
  client.set_timeout @persistent_timeout
404
- @reactor.add client
405
- return
461
+ if @reactor.add client
462
+ close_socket = false
463
+ break
464
+ end
406
465
  end
407
466
  end
408
467
  end
409
-
410
- # The client disconnected while we were reading data
411
- rescue ConnectionError
412
- # Swallow them. The ensure tries to close +client+ down
413
-
414
- # SSL handshake error
415
- rescue MiniSSL::SSLError => e
416
- lowlevel_error(e, client.env)
417
-
418
- ssl_socket = client.io
419
- addr = ssl_socket.peeraddr.last
420
- cert = ssl_socket.peercert
421
-
422
- close_socket = true
423
-
424
- @events.ssl_error e, addr, cert
425
-
426
- # The client doesn't know HTTP well
427
- rescue HttpParserError => e
428
- lowlevel_error(e, client.env)
429
-
430
- client.write_error(400)
431
-
432
- @events.parse_error e, client
433
-
434
- # Server error
468
+ true
435
469
  rescue StandardError => e
436
- lowlevel_error(e, client.env)
437
-
438
- client.write_error(500)
439
-
440
- @events.unknown_error e, nil, "Read"
470
+ client_error(e, client)
471
+ # The ensure tries to close +client+ down
472
+ requests > 0
441
473
  ensure
442
474
  buffer.reset
443
475
 
@@ -452,348 +484,15 @@ module Puma
452
484
  end
453
485
  end
454
486
 
455
- # Given a Hash +env+ for the request read from +client+, add
456
- # and fixup keys to comply with Rack's env guidelines.
457
- #
458
- def normalize_env(env, client)
459
- if host = env[HTTP_HOST]
460
- if colon = host.index(":")
461
- env[SERVER_NAME] = host[0, colon]
462
- env[SERVER_PORT] = host[colon+1, host.bytesize]
463
- else
464
- env[SERVER_NAME] = host
465
- env[SERVER_PORT] = default_server_port(env)
466
- end
467
- else
468
- env[SERVER_NAME] = LOCALHOST
469
- env[SERVER_PORT] = default_server_port(env)
470
- end
471
-
472
- unless env[REQUEST_PATH]
473
- # it might be a dumbass full host request header
474
- uri = URI.parse(env[REQUEST_URI])
475
- env[REQUEST_PATH] = uri.path
476
-
477
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
478
-
479
- # A nil env value will cause a LintError (and fatal errors elsewhere),
480
- # so only set the env value if there actually is a value.
481
- env[QUERY_STRING] = uri.query if uri.query
482
- end
483
-
484
- env[PATH_INFO] = env[REQUEST_PATH]
485
-
486
- # From https://www.ietf.org/rfc/rfc3875 :
487
- # "Script authors should be aware that the REMOTE_ADDR and
488
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
489
- # may not identify the ultimate source of the request.
490
- # They identify the client for the immediate request to the
491
- # server; that client may be a proxy, gateway, or other
492
- # intermediary acting on behalf of the actual source client."
493
- #
494
-
495
- unless env.key?(REMOTE_ADDR)
496
- begin
497
- addr = client.peerip
498
- rescue Errno::ENOTCONN
499
- # Client disconnects can result in an inability to get the
500
- # peeraddr from the socket; default to localhost.
501
- addr = LOCALHOST_IP
502
- end
503
-
504
- # Set unix socket addrs to localhost
505
- addr = LOCALHOST_IP if addr.empty?
506
-
507
- env[REMOTE_ADDR] = addr
508
- end
509
- end
510
-
511
- def default_server_port(env)
512
- 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"
513
- PORT_443
514
- else
515
- PORT_80
516
- end
517
- end
518
-
519
- # Takes the request +req+, invokes the Rack application to construct
520
- # the response and writes it back to +req.io+.
521
- #
522
- # The second parameter +lines+ is a IO-like object unique to this thread.
523
- # This is normally an instance of Puma::IOBuffer.
524
- #
525
- # It'll return +false+ when the connection is closed, this doesn't mean
526
- # that the response wasn't successful.
527
- #
528
- # It'll return +:async+ if the connection remains open but will be handled
529
- # elsewhere, i.e. the connection has been hijacked by the Rack application.
530
- #
531
- # Finally, it'll return +true+ on keep-alive connections.
532
- def handle_request(req, lines)
533
- @requests_count +=1
534
-
535
- env = req.env
536
- client = req.io
537
-
538
- return false if closed_socket?(client)
539
-
540
- normalize_env env, req
541
-
542
- env[PUMA_SOCKET] = client
543
-
544
- if env[HTTPS_KEY] && client.peercert
545
- env[PUMA_PEERCERT] = client.peercert
546
- end
547
-
548
- env[HIJACK_P] = true
549
- env[HIJACK] = req
550
-
551
- body = req.body
552
-
553
- head = env[REQUEST_METHOD] == HEAD
554
-
555
- env[RACK_INPUT] = body
556
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
557
-
558
- if @early_hints
559
- env[EARLY_HINTS] = lambda { |headers|
560
- begin
561
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
562
-
563
- headers.each_pair do |k, vs|
564
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
565
- vs.to_s.split(NEWLINE).each do |v|
566
- next if possible_header_injection?(v)
567
- fast_write client, "#{k}: #{v}\r\n"
568
- end
569
- else
570
- fast_write client, "#{k}: #{vs}\r\n"
571
- end
572
- end
573
-
574
- fast_write client, "\r\n".freeze
575
- rescue ConnectionError => e
576
- @events.debug_error e
577
- # noop, if we lost the socket we just won't send the early hints
578
- end
579
- }
580
- end
581
-
582
- # Fixup any headers with , in the name to have _ now. We emit
583
- # headers with , in them during the parse phase to avoid ambiguity
584
- # with the - to _ conversion for critical headers. But here for
585
- # compatibility, we'll convert them back. This code is written to
586
- # avoid allocation in the common case (ie there are no headers
587
- # with , in their names), that's why it has the extra conditionals.
588
-
589
- to_delete = nil
590
- to_add = nil
591
-
592
- env.each do |k,v|
593
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
594
- if to_delete
595
- to_delete << k
596
- else
597
- to_delete = [k]
598
- end
599
-
600
- unless to_add
601
- to_add = {}
602
- end
603
-
604
- to_add[k.tr(",", "_")] = v
605
- end
606
- end
607
-
608
- if to_delete
609
- to_delete.each { |k| env.delete(k) }
610
- env.merge! to_add
611
- end
612
-
613
- # A rack extension. If the app writes #call'ables to this
614
- # array, we will invoke them when the request is done.
615
- #
616
- after_reply = env[RACK_AFTER_REPLY] = []
617
-
618
- begin
619
- begin
620
- status, headers, res_body = @app.call(env)
621
-
622
- return :async if req.hijacked
623
-
624
- status = status.to_i
625
-
626
- if status == -1
627
- unless headers.empty? and res_body == []
628
- raise "async response must have empty headers and body"
629
- end
630
-
631
- return :async
632
- end
633
- rescue ThreadPool::ForceShutdown => e
634
- @events.unknown_error e, req, "Rack app"
635
- @events.log "Detected force shutdown of a thread"
636
-
637
- status, headers, res_body = lowlevel_error(e, env, 503)
638
- rescue Exception => e
639
- @events.unknown_error e, req, "Rack app"
640
-
641
- status, headers, res_body = lowlevel_error(e, env, 500)
642
- end
643
-
644
- content_length = nil
645
- no_body = head
646
-
647
- if res_body.kind_of? Array and res_body.size == 1
648
- content_length = res_body[0].bytesize
649
- end
650
-
651
- cork_socket client
652
-
653
- line_ending = LINE_END
654
- colon = COLON
655
-
656
- http_11 = env[HTTP_VERSION] == HTTP_11
657
- if http_11
658
- allow_chunked = true
659
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
660
-
661
- # An optimization. The most common response is 200, so we can
662
- # reply with the proper 200 status without having to compute
663
- # the response header.
664
- #
665
- if status == 200
666
- lines << HTTP_11_200
667
- else
668
- lines.append "HTTP/1.1 ", status.to_s, " ",
669
- fetch_status_code(status), line_ending
670
-
671
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
672
- end
673
- else
674
- allow_chunked = false
675
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
676
-
677
- # Same optimization as above for HTTP/1.1
678
- #
679
- if status == 200
680
- lines << HTTP_10_200
681
- else
682
- lines.append "HTTP/1.0 ", status.to_s, " ",
683
- fetch_status_code(status), line_ending
684
-
685
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
686
- end
687
- end
688
-
689
- # regardless of what the client wants, we always close the connection
690
- # if running without request queueing
691
- keep_alive &&= @queue_requests
692
-
693
- response_hijack = nil
694
-
695
- headers.each do |k, vs|
696
- case k.downcase
697
- when CONTENT_LENGTH2
698
- next if possible_header_injection?(vs)
699
- content_length = vs
700
- next
701
- when TRANSFER_ENCODING
702
- allow_chunked = false
703
- content_length = nil
704
- when HIJACK
705
- response_hijack = vs
706
- next
707
- end
708
-
709
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
710
- vs.to_s.split(NEWLINE).each do |v|
711
- next if possible_header_injection?(v)
712
- lines.append k, colon, v, line_ending
713
- end
714
- else
715
- lines.append k, colon, line_ending
716
- end
717
- end
718
-
719
- # HTTP/1.1 & 1.0 assume different defaults:
720
- # - HTTP 1.0 assumes the connection will be closed if not specified
721
- # - HTTP 1.1 assumes the connection will be kept alive if not specified.
722
- # Only set the header if we're doing something which is not the default
723
- # for this protocol version
724
- if http_11
725
- lines << CONNECTION_CLOSE if !keep_alive
726
- else
727
- lines << CONNECTION_KEEP_ALIVE if keep_alive
728
- end
729
-
730
- if no_body
731
- if content_length and status != 204
732
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
733
- end
734
-
735
- lines << line_ending
736
- fast_write client, lines.to_s
737
- return keep_alive
738
- end
739
-
740
- if content_length
741
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
742
- chunked = false
743
- elsif !response_hijack and allow_chunked
744
- lines << TRANSFER_ENCODING_CHUNKED
745
- chunked = true
746
- end
747
-
748
- lines << line_ending
749
-
750
- fast_write client, lines.to_s
751
-
752
- if response_hijack
753
- response_hijack.call client
754
- return :async
755
- end
756
-
757
- begin
758
- res_body.each do |part|
759
- next if part.bytesize.zero?
760
- if chunked
761
- fast_write client, part.bytesize.to_s(16)
762
- fast_write client, line_ending
763
- fast_write client, part
764
- fast_write client, line_ending
765
- else
766
- fast_write client, part
767
- end
768
-
769
- client.flush
770
- end
771
-
772
- if chunked
773
- fast_write client, CLOSE_CHUNKED
774
- client.flush
775
- end
776
- rescue SystemCallError, IOError
777
- raise ConnectionError, "Connection error detected during write"
778
- end
779
-
780
- ensure
781
- uncork_socket client
782
-
783
- body.close
784
- req.tempfile.unlink if req.tempfile
785
- res_body.close if res_body.respond_to? :close
786
-
787
- after_reply.each { |o| o.call }
788
- end
789
-
790
- return keep_alive
487
+ # Triggers a client timeout if the thread-pool shuts down
488
+ # during execution of the provided block.
489
+ def with_force_shutdown(client, &block)
490
+ @thread_pool.with_force_shutdown(&block)
491
+ rescue ThreadPool::ForceShutdown
492
+ client.timeout!
791
493
  end
792
494
 
793
- def fetch_status_code(status)
794
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
795
- end
796
- private :fetch_status_code
495
+ # :nocov:
797
496
 
798
497
  # Given the request +env+ from +client+ and the partial body +body+
799
498
  # plus a potential Content-Length value +cl+, finish reading
@@ -801,6 +500,7 @@ module Puma
801
500
  #
802
501
  # If the body is larger than MAX_BODY, a Tempfile object is used
803
502
  # for the body, otherwise a StringIO is used.
503
+ # @deprecated 6.0.0
804
504
  #
805
505
  def read_body(env, client, body, cl)
806
506
  content_length = cl.to_i
@@ -833,7 +533,7 @@ module Puma
833
533
 
834
534
  remain -= stream.write(chunk)
835
535
 
836
- # Raed the rest of the chunks
536
+ # Read the rest of the chunks
837
537
  while remain > 0
838
538
  chunk = client.readpartial(CHUNK_SIZE)
839
539
  unless chunk
@@ -848,6 +548,25 @@ module Puma
848
548
 
849
549
  return stream
850
550
  end
551
+ # :nocov:
552
+
553
+ # Handle various error types thrown by Client I/O operations.
554
+ def client_error(e, client)
555
+ # Swallow, do not log
556
+ return if [ConnectionError, EOFError].include?(e.class)
557
+
558
+ lowlevel_error(e, client.env)
559
+ case e
560
+ when MiniSSL::SSLError
561
+ @events.ssl_error e, client.io
562
+ when HttpParserError
563
+ client.write_error(400)
564
+ @events.parse_error e, client
565
+ else
566
+ client.write_error(500)
567
+ @events.unknown_error e, nil, "Read"
568
+ end
569
+ end
851
570
 
852
571
  # A fallback rack response if +@app+ raises as exception.
853
572
  #
@@ -915,7 +634,7 @@ module Puma
915
634
 
916
635
  if @thread_pool
917
636
  if timeout = @options[:force_shutdown_after]
918
- @thread_pool.shutdown timeout.to_i
637
+ @thread_pool.shutdown timeout.to_f
919
638
  else
920
639
  @thread_pool.shutdown
921
640
  end
@@ -923,19 +642,16 @@ module Puma
923
642
  end
924
643
 
925
644
  def notify_safely(message)
926
- @check, @notify = Puma::Util.pipe unless @notify
927
- begin
928
- @notify << message
929
- rescue IOError, NoMethodError, Errno::EPIPE
930
- # The server, in another thread, is shutting down
645
+ @notify << message
646
+ rescue IOError, NoMethodError, Errno::EPIPE
647
+ # The server, in another thread, is shutting down
648
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
649
+ rescue RuntimeError => e
650
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
651
+ if e.message.include?('IOError')
931
652
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
932
- rescue RuntimeError => e
933
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
934
- if e.message.include?('IOError')
935
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
936
- else
937
- raise e
938
- end
653
+ else
654
+ raise e
939
655
  end
940
656
  end
941
657
  private :notify_safely
@@ -958,48 +674,17 @@ module Puma
958
674
  @thread.join if @thread && sync
959
675
  end
960
676
 
961
- def fast_write(io, str)
962
- n = 0
963
- while true
964
- begin
965
- n = io.syswrite str
966
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
967
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
968
- raise ConnectionError, "Socket timeout writing data"
969
- end
970
-
971
- retry
972
- rescue Errno::EPIPE, SystemCallError, IOError
973
- raise ConnectionError, "Socket timeout writing data"
974
- end
975
-
976
- return if n == str.bytesize
977
- str = str.byteslice(n..-1)
978
- end
979
- end
980
- private :fast_write
981
-
982
- ThreadLocalKey = :puma_server
983
-
984
- def self.current
985
- Thread.current[ThreadLocalKey]
986
- end
987
-
988
677
  def shutting_down?
989
678
  @status == :stop || @status == :restart
990
679
  end
991
680
 
992
- def possible_header_injection?(header_value)
993
- HTTP_INJECTION_REGEX =~ header_value.to_s
994
- end
995
- private :possible_header_injection?
996
-
997
681
  # List of methods invoked by #stats.
998
682
  # @version 5.0.0
999
683
  STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1000
684
 
1001
685
  # Returns a hash of stats about the running server for reporting purposes.
1002
686
  # @version 5.0.0
687
+ # @!attribute [r] stats
1003
688
  def stats
1004
689
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1005
690
  end