puma 5.0.2-java → 5.2.0-java

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