puma 3.12.1 → 5.6.7

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