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