puma 3.12.1 → 5.6.4

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1553 -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/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +95 -120
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +51 -1
  28. data/ext/puma_http11/http11_parser.c +105 -117
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +4 -2
  32. data/ext/puma_http11/http11_parser_common.rl +4 -4
  33. data/ext/puma_http11/mini_ssl.c +319 -96
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +120 -65
  38. data/ext/puma_http11/puma_http11.c +35 -51
  39. data/lib/puma/app/status.rb +68 -49
  40. data/lib/puma/binder.rb +234 -137
  41. data/lib/puma/cli.rb +28 -18
  42. data/lib/puma/client.rb +343 -230
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +247 -232
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +61 -51
  48. data/lib/puma/const.rb +42 -21
  49. data/lib/puma/control_cli.rb +109 -67
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +615 -123
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -31
  54. data/lib/puma/io_buffer.rb +7 -5
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +182 -69
  58. data/lib/puma/minissl/context_builder.rb +81 -0
  59. data/lib/puma/minissl.rb +161 -61
  60. data/lib/puma/null_io.rb +13 -1
  61. data/lib/puma/plugin/tmp_restart.rb +2 -0
  62. data/lib/puma/plugin.rb +7 -13
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +3 -5
  65. data/lib/puma/rack/urlmap.rb +2 -0
  66. data/lib/puma/rack_default.rb +2 -0
  67. data/lib/puma/reactor.rb +85 -316
  68. data/lib/puma/request.rb +472 -0
  69. data/lib/puma/runner.rb +48 -55
  70. data/lib/puma/server.rb +303 -695
  71. data/lib/puma/single.rb +11 -67
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +132 -82
  75. data/lib/puma/util.rb +21 -7
  76. data/lib/puma.rb +54 -0
  77. data/lib/rack/handler/puma.rb +5 -6
  78. data/tools/Dockerfile +16 -0
  79. data/tools/trickletest.rb +0 -1
  80. metadata +45 -29
  81. data/ext/puma_http11/io_buffer.c +0 -155
  82. data/lib/puma/accept_nonblock.rb +0 -23
  83. data/lib/puma/compat.rb +0 -14
  84. data/lib/puma/convenient.rb +0 -25
  85. data/lib/puma/daemon_ext.rb +0 -33
  86. data/lib/puma/delegation.rb +0 -13
  87. data/lib/puma/java_io_buffer.rb +0 -47
  88. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
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,31 @@ 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
+
43
+ # @todo the following may be deprecated in the future
44
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
45
+ :leak_stack_on_error,
46
+ :persistent_timeout, :reaping_time
47
+
48
+ # @deprecated v6.0.0
49
+ attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
50
+ :leak_stack_on_error, :min_threads, :max_threads,
51
+ :persistent_timeout, :reaping_time
52
+
44
53
  attr_accessor :app
54
+ attr_accessor :binder
55
+
56
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
57
+ :add_unix_listener, :connected_ports
45
58
 
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
59
+ ThreadLocalKey = :puma_server
52
60
 
53
61
  # Create a server for the rack app +app+.
54
62
  #
@@ -58,90 +66,117 @@ module Puma
58
66
  # Server#run returns a thread that you can join on to wait for the server
59
67
  # to do its work.
60
68
  #
69
+ # @note Several instance variables exist so they are available for testing,
70
+ # and have default values set via +fetch+. Normally the values are set via
71
+ # `::Puma::Configuration.puma_default_options`.
72
+ #
61
73
  def initialize(app, events=Events.stdio, options={})
62
74
  @app = app
63
75
  @events = events
64
76
 
65
- @check, @notify = Puma::Util.pipe
66
-
77
+ @check, @notify = nil
67
78
  @status = :stop
68
79
 
69
- @min_threads = 0
70
- @max_threads = 16
71
80
  @auto_trim_time = 30
72
81
  @reaping_time = 1
73
82
 
74
83
  @thread = nil
75
84
  @thread_pool = nil
76
- @early_hints = nil
77
85
 
78
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
79
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
86
+ @options = options
80
87
 
81
- @binder = Binder.new(events)
82
- @own_binder = true
88
+ @early_hints = options.fetch :early_hints, nil
89
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
90
+ @min_threads = options.fetch :min_threads, 0
91
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
92
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
93
+ @queue_requests = options.fetch :queue_requests, true
94
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
95
+ @io_selector_backend = options.fetch :io_selector_backend, :auto
83
96
 
84
- @leak_stack_on_error = true
97
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
98
+ @leak_stack_on_error = @options[:environment] ? temp : true
85
99
 
86
- @options = options
87
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
100
+ @binder = Binder.new(events)
88
101
 
89
102
  ENV['RACK_ENV'] ||= "development"
90
103
 
91
104
  @mode = :http
92
105
 
93
106
  @precheck_closing = true
94
- end
95
107
 
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
108
+ @requests_count = 0
109
+ end
102
110
 
103
111
  def inherit_binder(bind)
104
112
  @binder = bind
105
- @own_binder = false
106
113
  end
107
114
 
108
- def tcp_mode!
109
- @mode = :tcp
115
+ class << self
116
+ # @!attribute [r] current
117
+ def current
118
+ Thread.current[ThreadLocalKey]
119
+ end
120
+
121
+ # :nodoc:
122
+ # @version 5.0.0
123
+ def tcp_cork_supported?
124
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
125
+ end
126
+
127
+ # :nodoc:
128
+ # @version 5.0.0
129
+ def closed_socket_supported?
130
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
131
+ end
132
+ private :tcp_cork_supported?
133
+ private :closed_socket_supported?
110
134
  end
111
135
 
112
136
  # On Linux, use TCP_CORK to better control how the TCP stack
113
137
  # packetizes our stream. This improves both latency and throughput.
138
+ # socket parameter may be an MiniSSL::Socket, so use to_io
114
139
  #
115
- if RUBY_PLATFORM =~ /linux/
116
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
117
-
140
+ if tcp_cork_supported?
118
141
  # 6 == Socket::IPPROTO_TCP
119
142
  # 3 == TCP_CORK
120
143
  # 1/0 == turn on/off
121
144
  def cork_socket(socket)
145
+ skt = socket.to_io
122
146
  begin
123
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
147
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
124
148
  rescue IOError, SystemCallError
125
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
+ Puma::Util.purge_interrupt_queue
126
150
  end
127
151
  end
128
152
 
129
153
  def uncork_socket(socket)
154
+ skt = socket.to_io
130
155
  begin
131
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
156
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
132
157
  rescue IOError, SystemCallError
133
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
158
+ Puma::Util.purge_interrupt_queue
134
159
  end
135
160
  end
161
+ else
162
+ def cork_socket(socket)
163
+ end
164
+
165
+ def uncork_socket(socket)
166
+ end
167
+ end
168
+
169
+ if closed_socket_supported?
170
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
136
171
 
137
172
  def closed_socket?(socket)
138
- return false unless socket.kind_of? TCPSocket
139
- return false unless @precheck_closing
173
+ skt = socket.to_io
174
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
140
175
 
141
176
  begin
142
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
177
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
143
178
  rescue IOError, SystemCallError
144
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
179
+ Puma::Util.purge_interrupt_queue
145
180
  @precheck_closing = false
146
181
  false
147
182
  else
@@ -151,21 +186,17 @@ module Puma
151
186
  end
152
187
  end
153
188
  else
154
- def cork_socket(socket)
155
- end
156
-
157
- def uncork_socket(socket)
158
- end
159
-
160
189
  def closed_socket?(socket)
161
190
  false
162
191
  end
163
192
  end
164
193
 
194
+ # @!attribute [r] backlog
165
195
  def backlog
166
196
  @thread_pool and @thread_pool.backlog
167
197
  end
168
198
 
199
+ # @!attribute [r] running
169
200
  def running
170
201
  @thread_pool and @thread_pool.spawned
171
202
  end
@@ -178,172 +209,38 @@ module Puma
178
209
  # there are 5 threads sitting idle ready to take
179
210
  # a request. If one request comes in, then the
180
211
  # value would be 4 until it finishes processing.
212
+ # @!attribute [r] pool_capacity
181
213
  def pool_capacity
182
214
  @thread_pool and @thread_pool.pool_capacity
183
215
  end
184
216
 
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
217
  # Runs the server.
283
218
  #
284
219
  # If +background+ is true (the default) then a thread is spun
285
220
  # up in the background to handle requests. Otherwise requests
286
221
  # are handled synchronously.
287
222
  #
288
- def run(background=true)
223
+ def run(background=true, thread_name: 'srv')
289
224
  BasicSocket.do_not_reverse_lookup = true
290
225
 
291
226
  @events.fire :state, :booting
292
227
 
293
228
  @status = :run
294
229
 
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
230
+ @thread_pool = ThreadPool.new(
231
+ thread_name,
232
+ @min_threads,
233
+ @max_threads,
234
+ ::Puma::IOBuffer,
235
+ &method(:process_client)
236
+ )
341
237
 
238
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
342
239
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
343
240
 
344
- if queue_requests
345
- @reactor = Reactor.new self, @thread_pool
346
- @reactor.run_in_thread
241
+ if @queue_requests
242
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
243
+ @reactor.run
347
244
  end
348
245
 
349
246
  if @reaping_time
@@ -354,86 +251,133 @@ module Puma
354
251
  @thread_pool.auto_trim!(@auto_trim_time)
355
252
  end
356
253
 
254
+ @check, @notify = Puma::Util.pipe unless @notify
255
+
357
256
  @events.fire :state, :running
358
257
 
359
258
  if background
360
- @thread = Thread.new { handle_servers }
259
+ @thread = Thread.new do
260
+ Puma.set_thread_name thread_name
261
+ handle_servers
262
+ end
361
263
  return @thread
362
264
  else
363
265
  handle_servers
364
266
  end
365
267
  end
366
268
 
269
+ # This method is called from the Reactor thread when a queued Client receives data,
270
+ # times out, or when the Reactor is shutting down.
271
+ #
272
+ # It is responsible for ensuring that a request has been completely received
273
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
274
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
275
+ # such as nginx) then the application would be subject to a slow client attack.
276
+ #
277
+ # 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).
278
+ #
279
+ # The method checks to see if it has the full header and body with
280
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
281
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
282
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
283
+ # The Client is then removed from the reactor (return `true`).
284
+ #
285
+ # If a client object times out, a 408 response is written, its connection is closed,
286
+ # and the object is removed from the reactor (return `true`).
287
+ #
288
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
289
+ # ThreadPool, depending on their current state (#can_close?).
290
+ #
291
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
292
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
293
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
294
+ def reactor_wakeup(client)
295
+ shutdown = !@queue_requests
296
+ if client.try_to_finish || (shutdown && !client.can_close?)
297
+ @thread_pool << client
298
+ elsif shutdown || client.timeout == 0
299
+ client.timeout!
300
+ else
301
+ client.set_timeout(@first_data_timeout)
302
+ false
303
+ end
304
+ rescue StandardError => e
305
+ client_error(e, client)
306
+ client.close
307
+ true
308
+ end
309
+
367
310
  def handle_servers
368
311
  begin
369
312
  check = @check
370
313
  sockets = [check] + @binder.ios
371
314
  pool = @thread_pool
372
315
  queue_requests = @queue_requests
316
+ drain = @options[:drain_on_shutdown] ? 0 : nil
373
317
 
374
- remote_addr_value = nil
375
- remote_addr_header = nil
376
-
377
- case @options[:remote_address]
318
+ addr_send_name, addr_value = case @options[:remote_address]
378
319
  when :value
379
- remote_addr_value = @options[:remote_address_value]
320
+ [:peerip=, @options[:remote_address_value]]
380
321
  when :header
381
- remote_addr_header = @options[:remote_address_header]
322
+ [:remote_addr_header=, @options[:remote_address_header]]
323
+ when :proxy_protocol
324
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
325
+ else
326
+ [nil, nil]
382
327
  end
383
328
 
384
- while @status == :run
329
+ while @status == :run || (drain && shutting_down?)
385
330
  begin
386
- ios = IO.select sockets
331
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
332
+ break unless ios
387
333
  ios.first.each do |sock|
388
334
  if sock == check
389
335
  break if handle_check
390
336
  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
337
+ pool.wait_until_not_full
338
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
339
+
340
+ io = begin
341
+ sock.accept_nonblock
342
+ rescue IO::WaitReadable
343
+ next
412
344
  end
345
+ drain += 1 if shutting_down?
346
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
347
+ c.listener = sock
348
+ c.send(addr_send_name, addr_value) if addr_value
349
+ }
413
350
  end
414
351
  end
415
- rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
352
+ rescue IOError, Errno::EBADF
353
+ # In the case that any of the sockets are unexpectedly close.
354
+ raise
355
+ rescue StandardError => e
356
+ @events.unknown_error e, nil, "Listen loop"
417
357
  end
418
358
  end
419
359
 
360
+ @events.debug "Drained #{drain} additional connections." if drain
420
361
  @events.fire :state, @status
421
362
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
363
  if queue_requests
424
- @reactor.clear!
364
+ @queue_requests = false
425
365
  @reactor.shutdown
426
366
  end
367
+ graceful_shutdown if @status == :stop || @status == :restart
427
368
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
369
+ @events.unknown_error e, nil, "Exception handling servers"
430
370
  ensure
431
- @check.close
432
- @notify.close
433
-
434
- if @status != :restart and @own_binder
435
- @binder.close
371
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
+ # Errno::EBADF is infrequently raised
373
+ [@check, @notify].each do |io|
374
+ begin
375
+ io.close unless io.closed?
376
+ rescue Errno::EBADF, RuntimeError
377
+ end
436
378
  end
379
+ @notify = nil
380
+ @check = nil
437
381
  end
438
382
 
439
383
  @events.fire :state, :done
@@ -455,450 +399,149 @@ module Puma
455
399
  return true
456
400
  end
457
401
 
458
- return false
402
+ false
459
403
  end
460
404
 
461
- # Given a connection on +client+, handle the incoming requests.
405
+ # Given a connection on +client+, handle the incoming requests,
406
+ # or queue the connection in the Reactor if no request is available.
462
407
  #
463
- # This method support HTTP Keep-Alive so it may, depending on if the client
408
+ # This method is called from a ThreadPool worker thread.
409
+ #
410
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
464
411
  # indicates that it supports keep alive, wait for another request before
465
412
  # returning.
466
413
  #
414
+ # Return true if one or more requests were processed.
467
415
  def process_client(client, buffer)
416
+ # Advertise this server into the thread
417
+ Thread.current[ThreadLocalKey] = self
418
+
419
+ clean_thread_locals = @options[:clean_thread_locals]
420
+ close_socket = true
421
+
422
+ requests = 0
423
+
468
424
  begin
425
+ if @queue_requests &&
426
+ !client.eagerly_finish
469
427
 
470
- clean_thread_locals = @options[:clean_thread_locals]
471
- close_socket = true
428
+ client.set_timeout(@first_data_timeout)
429
+ if @reactor.add client
430
+ close_socket = false
431
+ return false
432
+ end
433
+ end
434
+
435
+ with_force_shutdown(client) do
436
+ client.finish(@first_data_timeout)
437
+ end
472
438
 
473
439
  while true
474
- case handle_request(client, buffer)
440
+ @requests_count += 1
441
+ case handle_request(client, buffer, requests + 1)
475
442
  when false
476
- return
443
+ break
477
444
  when :async
478
445
  close_socket = false
479
- return
446
+ break
480
447
  when true
481
- return unless @queue_requests
482
448
  buffer.reset
483
449
 
484
450
  ThreadPool.clean_thread_locals if clean_thread_locals
485
451
 
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
452
+ requests += 1
510
453
 
511
- # The client doesn't know HTTP well
512
- rescue HttpParserError => e
513
- lowlevel_error(e, client.env)
454
+ # As an optimization, try to read the next request from the
455
+ # socket for a short time before returning to the reactor.
456
+ fast_check = @status == :run
514
457
 
515
- client.write_400
458
+ # Always pass the client back to the reactor after a reasonable
459
+ # number of inline requests if there are other requests pending.
460
+ fast_check = false if requests >= @max_fast_inline &&
461
+ @thread_pool.backlog > 0
516
462
 
517
- @events.parse_error self, client.env, e
463
+ next_request_ready = with_force_shutdown(client) do
464
+ client.reset(fast_check)
465
+ end
518
466
 
519
- # Server error
467
+ unless next_request_ready
468
+ break unless @queue_requests
469
+ client.set_timeout @persistent_timeout
470
+ if @reactor.add client
471
+ close_socket = false
472
+ break
473
+ end
474
+ end
475
+ end
476
+ end
477
+ true
520
478
  rescue StandardError => e
521
- lowlevel_error(e, client.env)
522
-
523
- client.write_500
524
-
525
- @events.unknown_error self, e, "Read"
526
-
479
+ client_error(e, client)
480
+ # The ensure tries to close +client+ down
481
+ requests > 0
527
482
  ensure
528
483
  buffer.reset
529
484
 
530
485
  begin
531
486
  client.close if close_socket
532
487
  rescue IOError, SystemCallError
533
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
534
489
  # Already closed
535
490
  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
491
+ @events.unknown_error e, nil, "Client"
733
492
  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
493
  end
824
-
825
- return keep_alive
826
494
  end
827
495
 
828
- def fetch_status_code(status)
829
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
496
+ # Triggers a client timeout if the thread-pool shuts down
497
+ # during execution of the provided block.
498
+ def with_force_shutdown(client, &block)
499
+ @thread_pool.with_force_shutdown(&block)
500
+ rescue ThreadPool::ForceShutdown
501
+ client.timeout!
830
502
  end
831
- private :fetch_status_code
832
503
 
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
504
+ # :nocov:
505
+
506
+ # Handle various error types thrown by Client I/O operations.
507
+ def client_error(e, client)
508
+ # Swallow, do not log
509
+ return if [ConnectionError, EOFError].include?(e.class)
510
+
511
+ lowlevel_error(e, client.env)
512
+ case e
513
+ when MiniSSL::SSLError
514
+ @events.ssl_error e, client.io
515
+ when HttpParserError
516
+ client.write_error(400)
517
+ @events.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @events.parse_error e, client
851
521
  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)
522
+ client.write_error(500)
523
+ @events.unknown_error e, nil, "Read"
880
524
  end
881
-
882
- stream.rewind
883
-
884
- return stream
885
525
  end
886
526
 
887
527
  # A fallback rack response if +@app+ raises as exception.
888
528
  #
889
- def lowlevel_error(e, env)
529
+ def lowlevel_error(e, env, status=500)
890
530
  if handler = @options[:lowlevel_error_handler]
891
531
  if handler.arity == 1
892
532
  return handler.call(e)
893
- else
533
+ elsif handler.arity == 2
894
534
  return handler.call(e, env)
535
+ else
536
+ return handler.call(e, env, status)
895
537
  end
896
538
  end
897
539
 
898
540
  if @leak_stack_on_error
899
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
541
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
900
543
  else
901
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
544
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
902
545
  end
903
546
  end
904
547
 
@@ -920,31 +563,13 @@ module Puma
920
563
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
921
564
  end
922
565
 
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."
566
+ if @status != :restart
567
+ @binder.close
943
568
  end
944
569
 
945
570
  if @thread_pool
946
571
  if timeout = @options[:force_shutdown_after]
947
- @thread_pool.shutdown timeout.to_i
572
+ @thread_pool.shutdown timeout.to_f
948
573
  else
949
574
  @thread_pool.shutdown
950
575
  end
@@ -952,18 +577,16 @@ module Puma
952
577
  end
953
578
 
954
579
  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
580
+ @notify << message
581
+ rescue IOError, NoMethodError, Errno::EPIPE
582
+ # The server, in another thread, is shutting down
583
+ Puma::Util.purge_interrupt_queue
584
+ rescue RuntimeError => e
585
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
586
+ if e.message.include?('IOError')
587
+ Puma::Util.purge_interrupt_queue
588
+ else
589
+ raise e
967
590
  end
968
591
  end
969
592
  private :notify_safely
@@ -981,39 +604,24 @@ module Puma
981
604
  @thread.join if @thread && sync
982
605
  end
983
606
 
984
- def begin_restart
607
+ def begin_restart(sync=false)
985
608
  notify_safely(RESTART_COMMAND)
609
+ @thread.join if @thread && sync
986
610
  end
987
611
 
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
612
+ def shutting_down?
613
+ @status == :stop || @status == :restart
1006
614
  end
1007
- private :fast_write
1008
615
 
1009
- ThreadLocalKey = :puma_server
616
+ # List of methods invoked by #stats.
617
+ # @version 5.0.0
618
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1010
619
 
1011
- def self.current
1012
- Thread.current[ThreadLocalKey]
1013
- end
1014
-
1015
- def shutting_down?
1016
- @status == :stop || @status == :restart
620
+ # Returns a hash of stats about the running server for reporting purposes.
621
+ # @version 5.0.0
622
+ # @!attribute [r] stats
623
+ def stats
624
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1017
625
  end
1018
626
  end
1019
627
  end