puma 3.12.0 → 5.3.1

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1413 -439
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +262 -87
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma/app/status.rb +68 -49
  37. data/lib/puma/binder.rb +197 -144
  38. data/lib/puma/cli.rb +17 -15
  39. data/lib/puma/client.rb +257 -226
  40. data/lib/puma/cluster/worker.rb +176 -0
  41. data/lib/puma/cluster/worker_handle.rb +90 -0
  42. data/lib/puma/cluster.rb +223 -212
  43. data/lib/puma/commonlogger.rb +4 -2
  44. data/lib/puma/configuration.rb +58 -51
  45. data/lib/puma/const.rb +41 -19
  46. data/lib/puma/control_cli.rb +117 -73
  47. data/lib/puma/detect.rb +26 -3
  48. data/lib/puma/dsl.rb +531 -123
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +57 -31
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +2 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +182 -70
  55. data/lib/puma/minissl/context_builder.rb +79 -0
  56. data/lib/puma/minissl.rb +149 -48
  57. data/lib/puma/null_io.rb +15 -1
  58. data/lib/puma/plugin/tmp_restart.rb +2 -0
  59. data/lib/puma/plugin.rb +8 -12
  60. data/lib/puma/queue_close.rb +26 -0
  61. data/lib/puma/rack/builder.rb +4 -5
  62. data/lib/puma/rack/urlmap.rb +2 -0
  63. data/lib/puma/rack_default.rb +2 -0
  64. data/lib/puma/reactor.rb +87 -316
  65. data/lib/puma/request.rb +456 -0
  66. data/lib/puma/runner.rb +33 -52
  67. data/lib/puma/server.rb +288 -679
  68. data/lib/puma/single.rb +13 -67
  69. data/lib/puma/state_file.rb +10 -3
  70. data/lib/puma/systemd.rb +46 -0
  71. data/lib/puma/thread_pool.rb +131 -81
  72. data/lib/puma/util.rb +14 -6
  73. data/lib/puma.rb +54 -0
  74. data/lib/rack/handler/puma.rb +8 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +45 -29
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -23
  82. data/lib/puma/daemon_ext.rb +0 -31
  83. data/lib/puma/delegation.rb +0 -11
  84. data/lib/puma/java_io_buffer.rb +0 -45
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -39
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/server.rb CHANGED
@@ -1,24 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
 
3
5
  require 'puma/thread_pool'
4
6
  require 'puma/const'
5
7
  require 'puma/events'
6
8
  require 'puma/null_io'
7
- require 'puma/compat'
8
9
  require 'puma/reactor'
9
10
  require 'puma/client'
10
11
  require 'puma/binder'
11
- require 'puma/delegation'
12
- require 'puma/accept_nonblock'
13
12
  require 'puma/util'
14
-
15
- require 'puma/puma_http11'
16
-
17
- unless Puma.const_defined? "IOBuffer"
18
- require 'puma/io_buffer'
19
- end
13
+ require 'puma/io_buffer'
14
+ require 'puma/request'
20
15
 
21
16
  require 'socket'
17
+ require 'forwardable'
22
18
 
23
19
  module Puma
24
20
 
@@ -26,7 +22,7 @@ module Puma
26
22
  #
27
23
  # This class is used by the `Puma::Single` and `Puma::Cluster` classes
28
24
  # to generate one or more `Puma::Server` instances capable of handling requests.
29
- # Each Puma process will contain one `Puma::Server` instacne.
25
+ # Each Puma process will contain one `Puma::Server` instance.
30
26
  #
31
27
  # The `Puma::Server` instance pulls requests from the socket, adds them to a
32
28
  # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
@@ -35,18 +31,31 @@ module Puma
35
31
  class Server
36
32
 
37
33
  include Puma::Const
38
- extend Puma::Delegation
34
+ include Request
35
+ extend Forwardable
39
36
 
40
37
  attr_reader :thread
41
38
  attr_reader :events
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
+
42
52
  attr_accessor :app
53
+ attr_accessor :binder
43
54
 
44
- attr_accessor :min_threads
45
- attr_accessor :max_threads
46
- attr_accessor :persistent_timeout
47
- attr_accessor :auto_trim_time
48
- attr_accessor :reaping_time
49
- 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
50
59
 
51
60
  # Create a server for the rack app +app+.
52
61
  #
@@ -56,88 +65,115 @@ module Puma
56
65
  # Server#run returns a thread that you can join on to wait for the server
57
66
  # to do its work.
58
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
+ #
59
72
  def initialize(app, events=Events.stdio, options={})
60
73
  @app = app
61
74
  @events = events
62
75
 
63
- @check, @notify = Puma::Util.pipe
64
-
76
+ @check, @notify = nil
65
77
  @status = :stop
66
78
 
67
- @min_threads = 0
68
- @max_threads = 16
69
79
  @auto_trim_time = 30
70
80
  @reaping_time = 1
71
81
 
72
82
  @thread = nil
73
83
  @thread_pool = nil
74
- @early_hints = nil
75
84
 
76
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
77
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
85
+ @options = options
78
86
 
79
- @binder = Binder.new(events)
80
- @own_binder = true
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
81
95
 
82
- @leak_stack_on_error = true
96
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
97
+ @leak_stack_on_error = @options[:environment] ? temp : true
83
98
 
84
- @options = options
85
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
99
+ @binder = Binder.new(events)
86
100
 
87
101
  ENV['RACK_ENV'] ||= "development"
88
102
 
89
103
  @mode = :http
90
104
 
91
105
  @precheck_closing = true
92
- end
93
-
94
- attr_accessor :binder, :leak_stack_on_error, :early_hints
95
106
 
96
- forward :add_tcp_listener, :@binder
97
- forward :add_ssl_listener, :@binder
98
- forward :add_unix_listener, :@binder
99
- forward :connected_port, :@binder
107
+ @requests_count = 0
108
+ end
100
109
 
101
110
  def inherit_binder(bind)
102
111
  @binder = bind
103
- @own_binder = false
104
112
  end
105
113
 
106
- def tcp_mode!
107
- @mode = :tcp
114
+ class << self
115
+ # @!attribute [r] current
116
+ def current
117
+ Thread.current[ThreadLocalKey]
118
+ end
119
+
120
+ # :nodoc:
121
+ # @version 5.0.0
122
+ def tcp_cork_supported?
123
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
124
+ end
125
+
126
+ # :nodoc:
127
+ # @version 5.0.0
128
+ def closed_socket_supported?
129
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
130
+ end
131
+ private :tcp_cork_supported?
132
+ private :closed_socket_supported?
108
133
  end
109
134
 
110
135
  # On Linux, use TCP_CORK to better control how the TCP stack
111
136
  # packetizes our stream. This improves both latency and throughput.
137
+ # socket parameter may be an MiniSSL::Socket, so use to_io
112
138
  #
113
- if RUBY_PLATFORM =~ /linux/
114
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
115
-
139
+ if tcp_cork_supported?
116
140
  # 6 == Socket::IPPROTO_TCP
117
141
  # 3 == TCP_CORK
118
142
  # 1/0 == turn on/off
119
143
  def cork_socket(socket)
144
+ skt = socket.to_io
120
145
  begin
121
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
146
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
122
147
  rescue IOError, SystemCallError
123
148
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
124
149
  end
125
150
  end
126
151
 
127
152
  def uncork_socket(socket)
153
+ skt = socket.to_io
128
154
  begin
129
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
155
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
130
156
  rescue IOError, SystemCallError
131
157
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
132
158
  end
133
159
  end
160
+ else
161
+ def cork_socket(socket)
162
+ end
163
+
164
+ def uncork_socket(socket)
165
+ end
166
+ end
167
+
168
+ if closed_socket_supported?
169
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
134
170
 
135
171
  def closed_socket?(socket)
136
- return false unless socket.kind_of? TCPSocket
137
- return false unless @precheck_closing
172
+ skt = socket.to_io
173
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
138
174
 
139
175
  begin
140
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
176
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
141
177
  rescue IOError, SystemCallError
142
178
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
143
179
  @precheck_closing = false
@@ -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,172 +208,37 @@ 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
182
215
 
183
- # Lopez Mode == raw tcp apps
184
-
185
- def run_lopez_mode(background=true)
186
- @thread_pool = ThreadPool.new(@min_threads,
187
- @max_threads,
188
- Hash) do |client, tl|
189
-
190
- io = client.to_io
191
- addr = io.peeraddr.last
192
-
193
- if addr.empty?
194
- # Set unix socket addrs to localhost
195
- addr = "127.0.0.1:0"
196
- else
197
- addr = "#{addr}:#{io.peeraddr[1]}"
198
- end
199
-
200
- env = { 'thread' => tl, REMOTE_ADDR => addr }
201
-
202
- begin
203
- @app.call env, client.to_io
204
- rescue Object => e
205
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
206
- STDERR.puts e.backtrace
207
- end
208
-
209
- client.close unless env['detach']
210
- end
211
-
212
- @events.fire :state, :running
213
-
214
- if background
215
- @thread = Thread.new { handle_servers_lopez_mode }
216
- return @thread
217
- else
218
- handle_servers_lopez_mode
219
- end
220
- end
221
-
222
- def handle_servers_lopez_mode
223
- begin
224
- check = @check
225
- sockets = [check] + @binder.ios
226
- pool = @thread_pool
227
-
228
- while @status == :run
229
- begin
230
- ios = IO.select sockets
231
- ios.first.each do |sock|
232
- if sock == check
233
- break if handle_check
234
- else
235
- begin
236
- if io = sock.accept_nonblock
237
- client = Client.new io, nil
238
- pool << client
239
- end
240
- rescue SystemCallError
241
- # nothing
242
- rescue Errno::ECONNABORTED
243
- # client closed the socket even before accept
244
- begin
245
- io.close
246
- rescue
247
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
248
- end
249
- end
250
- end
251
- end
252
- rescue Object => e
253
- @events.unknown_error self, e, "Listen loop"
254
- end
255
- end
256
-
257
- @events.fire :state, @status
258
-
259
- graceful_shutdown if @status == :stop || @status == :restart
260
-
261
- rescue Exception => e
262
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
263
- STDERR.puts e.backtrace
264
- ensure
265
- begin
266
- @check.close
267
- rescue
268
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
269
- end
270
-
271
- @notify.close
272
-
273
- if @status != :restart and @own_binder
274
- @binder.close
275
- end
276
- end
277
-
278
- @events.fire :state, :done
279
- end
280
216
  # Runs the server.
281
217
  #
282
218
  # If +background+ is true (the default) then a thread is spun
283
219
  # up in the background to handle requests. Otherwise requests
284
220
  # are handled synchronously.
285
221
  #
286
- def run(background=true)
222
+ def run(background=true, thread_name: 'server')
287
223
  BasicSocket.do_not_reverse_lookup = true
288
224
 
289
225
  @events.fire :state, :booting
290
226
 
291
227
  @status = :run
292
228
 
293
- if @mode == :tcp
294
- return run_lopez_mode(background)
295
- end
296
-
297
- queue_requests = @queue_requests
298
-
299
- @thread_pool = ThreadPool.new(@min_threads,
300
- @max_threads,
301
- IOBuffer) do |client, buffer|
302
-
303
- # Advertise this server into the thread
304
- Thread.current[ThreadLocalKey] = self
305
-
306
- process_now = false
307
-
308
- begin
309
- if queue_requests
310
- process_now = client.eagerly_finish
311
- else
312
- client.finish
313
- process_now = true
314
- end
315
- rescue MiniSSL::SSLError => e
316
- ssl_socket = client.io
317
- addr = ssl_socket.peeraddr.last
318
- cert = ssl_socket.peercert
319
-
320
- client.close
321
-
322
- @events.ssl_error self, addr, cert, e
323
- rescue HttpParserError => e
324
- client.write_400
325
- client.close
326
-
327
- @events.parse_error self, client.env, e
328
- rescue ConnectionError, EOFError
329
- client.close
330
- else
331
- if process_now
332
- process_client client, buffer
333
- else
334
- client.set_timeout @first_data_timeout
335
- @reactor.add client
336
- end
337
- end
338
- end
229
+ @thread_pool = ThreadPool.new(
230
+ @min_threads,
231
+ @max_threads,
232
+ ::Puma::IOBuffer,
233
+ &method(:process_client)
234
+ )
339
235
 
236
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
340
237
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
341
238
 
342
- if queue_requests
343
- @reactor = Reactor.new self, @thread_pool
344
- @reactor.run_in_thread
239
+ if @queue_requests
240
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
241
+ @reactor.run
345
242
  end
346
243
 
347
244
  if @reaping_time
@@ -352,22 +249,69 @@ module Puma
352
249
  @thread_pool.auto_trim!(@auto_trim_time)
353
250
  end
354
251
 
252
+ @check, @notify = Puma::Util.pipe unless @notify
253
+
355
254
  @events.fire :state, :running
356
255
 
357
256
  if background
358
- @thread = Thread.new { handle_servers }
257
+ @thread = Thread.new do
258
+ Puma.set_thread_name thread_name
259
+ handle_servers
260
+ end
359
261
  return @thread
360
262
  else
361
263
  handle_servers
362
264
  end
363
265
  end
364
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
+ else
299
+ client.set_timeout(@first_data_timeout)
300
+ false
301
+ end
302
+ rescue StandardError => e
303
+ client_error(e, client)
304
+ client.close
305
+ true
306
+ end
307
+
365
308
  def handle_servers
366
309
  begin
367
310
  check = @check
368
311
  sockets = [check] + @binder.ios
369
312
  pool = @thread_pool
370
313
  queue_requests = @queue_requests
314
+ drain = @options[:drain_on_shutdown] ? 0 : nil
371
315
 
372
316
  remote_addr_value = nil
373
317
  remote_addr_header = nil
@@ -379,59 +323,57 @@ module Puma
379
323
  remote_addr_header = @options[:remote_address_header]
380
324
  end
381
325
 
382
- while @status == :run
326
+ while @status == :run || (drain && shutting_down?)
383
327
  begin
384
- ios = IO.select sockets
328
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
329
+ break unless ios
385
330
  ios.first.each do |sock|
386
331
  if sock == check
387
332
  break if handle_check
388
333
  else
389
- begin
390
- if io = sock.accept_nonblock
391
- client = Client.new io, @binder.env(sock)
392
- if remote_addr_value
393
- client.peerip = remote_addr_value
394
- elsif remote_addr_header
395
- client.remote_addr_header = remote_addr_header
396
- end
397
-
398
- pool << client
399
- pool.wait_until_not_full
400
- end
401
- rescue SystemCallError
402
- # nothing
403
- rescue Errno::ECONNABORTED
404
- # client closed the socket even before accept
405
- begin
406
- io.close
407
- rescue
408
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
409
- end
334
+ pool.wait_until_not_full
335
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
336
+
337
+ io = begin
338
+ sock.accept_nonblock
339
+ rescue IO::WaitReadable
340
+ next
410
341
  end
342
+ drain += 1 if shutting_down?
343
+ client = Client.new io, @binder.env(sock)
344
+ if remote_addr_value
345
+ client.peerip = remote_addr_value
346
+ elsif remote_addr_header
347
+ client.remote_addr_header = remote_addr_header
348
+ end
349
+ pool << client
411
350
  end
412
351
  end
413
352
  rescue Object => e
414
- @events.unknown_error self, e, "Listen loop"
353
+ @events.unknown_error e, nil, "Listen loop"
415
354
  end
416
355
  end
417
356
 
357
+ @events.debug "Drained #{drain} additional connections." if drain
418
358
  @events.fire :state, @status
419
359
 
420
- graceful_shutdown if @status == :stop || @status == :restart
421
360
  if queue_requests
422
- @reactor.clear!
361
+ @queue_requests = false
423
362
  @reactor.shutdown
424
363
  end
364
+ graceful_shutdown if @status == :stop || @status == :restart
425
365
  rescue Exception => e
426
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
427
- STDERR.puts e.backtrace
366
+ @events.unknown_error e, nil, "Exception handling servers"
428
367
  ensure
429
- @check.close
430
- @notify.close
431
-
432
- if @status != :restart and @own_binder
433
- @binder.close
368
+ begin
369
+ @check.close unless @check.closed?
370
+ rescue Errno::EBADF, RuntimeError
371
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
+ # Errno::EBADF is infrequently raised
434
373
  end
374
+ @notify.close
375
+ @notify = nil
376
+ @check = nil
435
377
  end
436
378
 
437
379
  @events.fire :state, :done
@@ -456,72 +398,89 @@ module Puma
456
398
  return false
457
399
  end
458
400
 
459
- # Given a connection on +client+, handle the incoming requests.
401
+ # Given a connection on +client+, handle the incoming requests,
402
+ # or queue the connection in the Reactor if no request is available.
403
+ #
404
+ # This method is called from a ThreadPool worker thread.
460
405
  #
461
- # This method support HTTP Keep-Alive so it may, depending on if the client
406
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
462
407
  # indicates that it supports keep alive, wait for another request before
463
408
  # returning.
464
409
  #
410
+ # Return true if one or more requests were processed.
465
411
  def process_client(client, buffer)
412
+ # Advertise this server into the thread
413
+ Thread.current[ThreadLocalKey] = self
414
+
415
+ clean_thread_locals = @options[:clean_thread_locals]
416
+ close_socket = true
417
+
418
+ requests = 0
419
+
466
420
  begin
421
+ if @queue_requests &&
422
+ !client.eagerly_finish
467
423
 
468
- clean_thread_locals = @options[:clean_thread_locals]
469
- close_socket = true
424
+ client.set_timeout(@first_data_timeout)
425
+ if @reactor.add client
426
+ close_socket = false
427
+ return false
428
+ end
429
+ end
430
+
431
+ with_force_shutdown(client) do
432
+ client.finish(@first_data_timeout)
433
+ end
470
434
 
471
435
  while true
436
+ @requests_count += 1
472
437
  case handle_request(client, buffer)
473
438
  when false
474
- return
439
+ break
475
440
  when :async
476
441
  close_socket = false
477
- return
442
+ break
478
443
  when true
479
- return unless @queue_requests
480
444
  buffer.reset
481
445
 
482
446
  ThreadPool.clean_thread_locals if clean_thread_locals
483
447
 
484
- unless client.reset(@status == :run)
485
- close_socket = false
448
+ requests += 1
449
+
450
+ # Closing keepalive sockets after they've made a reasonable
451
+ # number of requests allows Puma to service many connections
452
+ # fairly, even when the number of concurrent connections exceeds
453
+ # the size of the threadpool. It also allows cluster mode Pumas
454
+ # to keep load evenly distributed across workers, because clients
455
+ # are randomly assigned a new worker when opening a new connection.
456
+ #
457
+ # Previously, Puma would kick connections in this conditional back
458
+ # to the reactor. However, because this causes the todo set to increase
459
+ # in size, the wait_until_full mutex would never unlock, leaving
460
+ # any additional connections unserviced.
461
+ break if requests >= @max_fast_inline
462
+
463
+ check_for_more_data = @status == :run
464
+
465
+ next_request_ready = with_force_shutdown(client) do
466
+ client.reset(check_for_more_data)
467
+ end
468
+
469
+ unless next_request_ready
470
+ break unless @queue_requests
486
471
  client.set_timeout @persistent_timeout
487
- @reactor.add client
488
- return
472
+ if @reactor.add client
473
+ close_socket = false
474
+ break
475
+ end
489
476
  end
490
477
  end
491
478
  end
492
-
493
- # The client disconnected while we were reading data
494
- rescue ConnectionError
495
- # Swallow them. The ensure tries to close +client+ down
496
-
497
- # SSL handshake error
498
- rescue MiniSSL::SSLError => e
499
- lowlevel_error(e, client.env)
500
-
501
- ssl_socket = client.io
502
- addr = ssl_socket.peeraddr.last
503
- cert = ssl_socket.peercert
504
-
505
- close_socket = true
506
-
507
- @events.ssl_error self, addr, cert, e
508
-
509
- # The client doesn't know HTTP well
510
- rescue HttpParserError => e
511
- lowlevel_error(e, client.env)
512
-
513
- client.write_400
514
-
515
- @events.parse_error self, client.env, e
516
-
517
- # Server error
479
+ true
518
480
  rescue StandardError => e
519
- lowlevel_error(e, client.env)
520
-
521
- client.write_500
522
-
523
- @events.unknown_error self, e, "Read"
524
-
481
+ client_error(e, client)
482
+ # The ensure tries to close +client+ down
483
+ requests > 0
525
484
  ensure
526
485
  buffer.reset
527
486
 
@@ -531,372 +490,57 @@ module Puma
531
490
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
532
491
  # Already closed
533
492
  rescue StandardError => e
534
- @events.unknown_error self, e, "Client"
535
- end
536
- end
537
- end
538
-
539
- # Given a Hash +env+ for the request read from +client+, add
540
- # and fixup keys to comply with Rack's env guidelines.
541
- #
542
- def normalize_env(env, client)
543
- if host = env[HTTP_HOST]
544
- if colon = host.index(":")
545
- env[SERVER_NAME] = host[0, colon]
546
- env[SERVER_PORT] = host[colon+1, host.bytesize]
547
- else
548
- env[SERVER_NAME] = host
549
- env[SERVER_PORT] = default_server_port(env)
550
- end
551
- else
552
- env[SERVER_NAME] = LOCALHOST
553
- env[SERVER_PORT] = default_server_port(env)
554
- end
555
-
556
- unless env[REQUEST_PATH]
557
- # it might be a dumbass full host request header
558
- uri = URI.parse(env[REQUEST_URI])
559
- env[REQUEST_PATH] = uri.path
560
-
561
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
562
-
563
- # A nil env value will cause a LintError (and fatal errors elsewhere),
564
- # so only set the env value if there actually is a value.
565
- env[QUERY_STRING] = uri.query if uri.query
566
- end
567
-
568
- env[PATH_INFO] = env[REQUEST_PATH]
569
-
570
- # From http://www.ietf.org/rfc/rfc3875 :
571
- # "Script authors should be aware that the REMOTE_ADDR and
572
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
573
- # may not identify the ultimate source of the request.
574
- # They identify the client for the immediate request to the
575
- # server; that client may be a proxy, gateway, or other
576
- # intermediary acting on behalf of the actual source client."
577
- #
578
-
579
- unless env.key?(REMOTE_ADDR)
580
- begin
581
- addr = client.peerip
582
- rescue Errno::ENOTCONN
583
- # Client disconnects can result in an inability to get the
584
- # peeraddr from the socket; default to localhost.
585
- addr = LOCALHOST_IP
493
+ @events.unknown_error e, nil, "Client"
586
494
  end
587
-
588
- # Set unix socket addrs to localhost
589
- addr = LOCALHOST_IP if addr.empty?
590
-
591
- env[REMOTE_ADDR] = addr
592
495
  end
593
496
  end
594
497
 
595
- def default_server_port(env)
596
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
597
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
498
+ # Triggers a client timeout if the thread-pool shuts down
499
+ # during execution of the provided block.
500
+ def with_force_shutdown(client, &block)
501
+ @thread_pool.with_force_shutdown(&block)
502
+ rescue ThreadPool::ForceShutdown
503
+ client.timeout!
598
504
  end
599
505
 
600
- # Given the request +env+ from +client+ and a partial request body
601
- # in +body+, finish reading the body if there is one and invoke
602
- # the rack app. Then construct the response and write it back to
603
- # +client+
604
- #
605
- # +cl+ is the previously fetched Content-Length header if there
606
- # was one. This is an optimization to keep from having to look
607
- # it up again.
608
- #
609
- def handle_request(req, lines)
610
- env = req.env
611
- client = req.io
612
-
613
- return false if closed_socket?(client)
506
+ # :nocov:
614
507
 
615
- normalize_env env, req
508
+ # Handle various error types thrown by Client I/O operations.
509
+ def client_error(e, client)
510
+ # Swallow, do not log
511
+ return if [ConnectionError, EOFError].include?(e.class)
616
512
 
617
- env[PUMA_SOCKET] = client
618
-
619
- if env[HTTPS_KEY] && client.peercert
620
- env[PUMA_PEERCERT] = client.peercert
621
- end
622
-
623
- env[HIJACK_P] = true
624
- env[HIJACK] = req
625
-
626
- body = req.body
627
-
628
- head = env[REQUEST_METHOD] == HEAD
629
-
630
- env[RACK_INPUT] = body
631
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
632
-
633
- if @early_hints
634
- env[EARLY_HINTS] = lambda { |headers|
635
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
636
-
637
- headers.each_pair do |k, vs|
638
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
639
- vs.to_s.split(NEWLINE).each do |v|
640
- fast_write client, "#{k}: #{v}\r\n"
641
- end
642
- else
643
- fast_write client, "#{k}: #{vs}\r\n"
644
- end
645
- end
646
-
647
- fast_write client, "\r\n".freeze
648
- }
649
- end
650
-
651
- # A rack extension. If the app writes #call'ables to this
652
- # array, we will invoke them when the request is done.
653
- #
654
- after_reply = env[RACK_AFTER_REPLY] = []
655
-
656
- begin
657
- begin
658
- status, headers, res_body = @app.call(env)
659
-
660
- return :async if req.hijacked
661
-
662
- status = status.to_i
663
-
664
- if status == -1
665
- unless headers.empty? and res_body == []
666
- raise "async response must have empty headers and body"
667
- end
668
-
669
- return :async
670
- end
671
- rescue ThreadPool::ForceShutdown => e
672
- @events.log "Detected force shutdown of a thread, returning 503"
673
- @events.unknown_error self, e, "Rack app"
674
-
675
- status = 503
676
- headers = {}
677
- res_body = ["Request was internally terminated early\n"]
678
-
679
- rescue Exception => e
680
- @events.unknown_error self, e, "Rack app", env
681
-
682
- status, headers, res_body = lowlevel_error(e, env)
683
- end
684
-
685
- content_length = nil
686
- no_body = head
687
-
688
- if res_body.kind_of? Array and res_body.size == 1
689
- content_length = res_body[0].bytesize
690
- end
691
-
692
- cork_socket client
693
-
694
- line_ending = LINE_END
695
- colon = COLON
696
-
697
- http_11 = if env[HTTP_VERSION] == HTTP_11
698
- allow_chunked = true
699
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
700
- include_keepalive_header = false
701
-
702
- # An optimization. The most common response is 200, so we can
703
- # reply with the proper 200 status without having to compute
704
- # the response header.
705
- #
706
- if status == 200
707
- lines << HTTP_11_200
708
- else
709
- lines.append "HTTP/1.1 ", status.to_s, " ",
710
- fetch_status_code(status), line_ending
711
-
712
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
713
- end
714
- true
715
- else
716
- allow_chunked = false
717
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
718
- include_keepalive_header = keep_alive
719
-
720
- # Same optimization as above for HTTP/1.1
721
- #
722
- if status == 200
723
- lines << HTTP_10_200
724
- else
725
- lines.append "HTTP/1.0 ", status.to_s, " ",
726
- fetch_status_code(status), line_ending
727
-
728
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
729
- end
730
- false
731
- end
732
-
733
- response_hijack = nil
734
-
735
- headers.each do |k, vs|
736
- case k.downcase
737
- when CONTENT_LENGTH2
738
- content_length = vs
739
- next
740
- when TRANSFER_ENCODING
741
- allow_chunked = false
742
- content_length = nil
743
- when HIJACK
744
- response_hijack = vs
745
- next
746
- end
747
-
748
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
749
- vs.to_s.split(NEWLINE).each do |v|
750
- lines.append k, colon, v, line_ending
751
- end
752
- else
753
- lines.append k, colon, line_ending
754
- end
755
- end
756
-
757
- if include_keepalive_header
758
- lines << CONNECTION_KEEP_ALIVE
759
- elsif http_11 && !keep_alive
760
- lines << CONNECTION_CLOSE
761
- end
762
-
763
- if no_body
764
- if content_length and status != 204
765
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
766
- end
767
-
768
- lines << line_ending
769
- fast_write client, lines.to_s
770
- return keep_alive
771
- end
772
-
773
- if content_length
774
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
775
- chunked = false
776
- elsif !response_hijack and allow_chunked
777
- lines << TRANSFER_ENCODING_CHUNKED
778
- chunked = true
779
- end
780
-
781
- lines << line_ending
782
-
783
- fast_write client, lines.to_s
784
-
785
- if response_hijack
786
- response_hijack.call client
787
- return :async
788
- end
789
-
790
- begin
791
- res_body.each do |part|
792
- next if part.bytesize.zero?
793
- if chunked
794
- fast_write client, part.bytesize.to_s(16)
795
- fast_write client, line_ending
796
- fast_write client, part
797
- fast_write client, line_ending
798
- else
799
- fast_write client, part
800
- end
801
-
802
- client.flush
803
- end
804
-
805
- if chunked
806
- fast_write client, CLOSE_CHUNKED
807
- client.flush
808
- end
809
- rescue SystemCallError, IOError
810
- raise ConnectionError, "Connection error detected during write"
811
- end
812
-
813
- ensure
814
- uncork_socket client
815
-
816
- body.close
817
- req.tempfile.unlink if req.tempfile
818
- res_body.close if res_body.respond_to? :close
819
-
820
- after_reply.each { |o| o.call }
821
- end
822
-
823
- return keep_alive
824
- end
825
-
826
- def fetch_status_code(status)
827
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
828
- end
829
- private :fetch_status_code
830
-
831
- # Given the request +env+ from +client+ and the partial body +body+
832
- # plus a potential Content-Length value +cl+, finish reading
833
- # the body and return it.
834
- #
835
- # If the body is larger than MAX_BODY, a Tempfile object is used
836
- # for the body, otherwise a StringIO is used.
837
- #
838
- def read_body(env, client, body, cl)
839
- content_length = cl.to_i
840
-
841
- remain = content_length - body.bytesize
842
-
843
- return StringIO.new(body) if remain <= 0
844
-
845
- # Use a Tempfile if there is a lot of data left
846
- if remain > MAX_BODY
847
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
848
- stream.binmode
513
+ lowlevel_error(e, client.env)
514
+ case e
515
+ when MiniSSL::SSLError
516
+ @events.ssl_error e, client.io
517
+ when HttpParserError
518
+ client.write_error(400)
519
+ @events.parse_error e, client
849
520
  else
850
- # The body[0,0] trick is to get an empty string in the same
851
- # encoding as body.
852
- stream = StringIO.new body[0,0]
853
- end
854
-
855
- stream.write body
856
-
857
- # Read an odd sized chunk so we can read even sized ones
858
- # after this
859
- chunk = client.readpartial(remain % CHUNK_SIZE)
860
-
861
- # No chunk means a closed socket
862
- unless chunk
863
- stream.close
864
- return nil
521
+ client.write_error(500)
522
+ @events.unknown_error e, nil, "Read"
865
523
  end
866
-
867
- remain -= stream.write(chunk)
868
-
869
- # Raed the rest of the chunks
870
- while remain > 0
871
- chunk = client.readpartial(CHUNK_SIZE)
872
- unless chunk
873
- stream.close
874
- return nil
875
- end
876
-
877
- remain -= stream.write(chunk)
878
- end
879
-
880
- stream.rewind
881
-
882
- return stream
883
524
  end
884
525
 
885
526
  # A fallback rack response if +@app+ raises as exception.
886
527
  #
887
- def lowlevel_error(e, env)
528
+ def lowlevel_error(e, env, status=500)
888
529
  if handler = @options[:lowlevel_error_handler]
889
530
  if handler.arity == 1
890
531
  return handler.call(e)
891
- else
532
+ elsif handler.arity == 2
892
533
  return handler.call(e, env)
534
+ else
535
+ return handler.call(e, env, status)
893
536
  end
894
537
  end
895
538
 
896
539
  if @leak_stack_on_error
897
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
540
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
541
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
898
542
  else
899
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
543
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
900
544
  end
901
545
  end
902
546
 
@@ -918,31 +562,13 @@ module Puma
918
562
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
919
563
  end
920
564
 
921
- if @options[:drain_on_shutdown]
922
- count = 0
923
-
924
- while true
925
- ios = IO.select @binder.ios, nil, nil, 0
926
- break unless ios
927
-
928
- ios.first.each do |sock|
929
- begin
930
- if io = sock.accept_nonblock
931
- count += 1
932
- client = Client.new io, @binder.env(sock)
933
- @thread_pool << client
934
- end
935
- rescue SystemCallError
936
- end
937
- end
938
- end
939
-
940
- @events.debug "Drained #{count} additional connections."
565
+ if @status != :restart
566
+ @binder.close
941
567
  end
942
568
 
943
569
  if @thread_pool
944
570
  if timeout = @options[:force_shutdown_after]
945
- @thread_pool.shutdown timeout.to_i
571
+ @thread_pool.shutdown timeout.to_f
946
572
  else
947
573
  @thread_pool.shutdown
948
574
  end
@@ -950,18 +576,16 @@ module Puma
950
576
  end
951
577
 
952
578
  def notify_safely(message)
953
- begin
954
- @notify << message
955
- rescue IOError
956
- # The server, in another thread, is shutting down
579
+ @notify << message
580
+ rescue IOError, NoMethodError, Errno::EPIPE
581
+ # The server, in another thread, is shutting down
582
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
583
+ rescue RuntimeError => e
584
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
585
+ if e.message.include?('IOError')
957
586
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
958
- rescue RuntimeError => e
959
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
960
- if e.message.include?('IOError')
961
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
962
- else
963
- raise e
964
- end
587
+ else
588
+ raise e
965
589
  end
966
590
  end
967
591
  private :notify_safely
@@ -979,39 +603,24 @@ module Puma
979
603
  @thread.join if @thread && sync
980
604
  end
981
605
 
982
- def begin_restart
606
+ def begin_restart(sync=false)
983
607
  notify_safely(RESTART_COMMAND)
608
+ @thread.join if @thread && sync
984
609
  end
985
610
 
986
- def fast_write(io, str)
987
- n = 0
988
- while true
989
- begin
990
- n = io.syswrite str
991
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
992
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
993
- raise ConnectionError, "Socket timeout writing data"
994
- end
995
-
996
- retry
997
- rescue Errno::EPIPE, SystemCallError, IOError
998
- raise ConnectionError, "Socket timeout writing data"
999
- end
1000
-
1001
- return if n == str.bytesize
1002
- str = str.byteslice(n..-1)
1003
- end
611
+ def shutting_down?
612
+ @status == :stop || @status == :restart
1004
613
  end
1005
- private :fast_write
1006
614
 
1007
- ThreadLocalKey = :puma_server
615
+ # List of methods invoked by #stats.
616
+ # @version 5.0.0
617
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1008
618
 
1009
- def self.current
1010
- Thread.current[ThreadLocalKey]
1011
- end
1012
-
1013
- def shutting_down?
1014
- @status == :stop || @status == :restart
619
+ # Returns a hash of stats about the running server for reporting purposes.
620
+ # @version 5.0.0
621
+ # @!attribute [r] stats
622
+ def stats
623
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1015
624
  end
1016
625
  end
1017
626
  end