puma 5.3.1-java → 5.5.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/puma/binder.rb CHANGED
@@ -41,6 +41,7 @@ module Puma
41
41
  "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
42
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
43
43
  "rack.run_once".freeze => false,
44
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
44
45
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
45
46
 
46
47
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -56,6 +57,7 @@ module Puma
56
57
 
57
58
  @envs = {}
58
59
  @ios = []
60
+ localhost_authority
59
61
  end
60
62
 
61
63
  attr_reader :ios
@@ -95,6 +97,7 @@ module Puma
95
97
  # @version 5.0.0
96
98
  #
97
99
  def create_activated_fds(env_hash)
100
+ @events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
98
101
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
102
  env_hash['LISTEN_FDS'].to_i.times do |index|
100
103
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -163,7 +166,7 @@ module Puma
163
166
  ios_len = @ios.length
164
167
  params = Util.parse_query uri.query
165
168
 
166
- opt = params.key?('low_latency')
169
+ opt = params.key?('low_latency') && params['low_latency'] != 'false'
167
170
  bak = params.fetch('backlog', 1024).to_i
168
171
 
169
172
  io = add_tcp_listener uri.host, uri.port, opt, bak
@@ -188,7 +191,8 @@ module Puma
188
191
  @unix_paths << path unless abstract
189
192
  io = inherit_unix_listener path, fd
190
193
  logger.log "* Inherited #{str}"
191
- elsif sock = @activated_sockets.delete([ :unix, path ])
194
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
195
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
192
196
  @unix_paths << path unless abstract || File.exist?(path)
193
197
  io = inherit_unix_listener path, sock
194
198
  logger.log "* Activated #{str}"
@@ -224,7 +228,13 @@ module Puma
224
228
  raise "Puma compiled without SSL support" unless HAS_SSL
225
229
 
226
230
  params = Util.parse_query uri.query
227
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
231
+
232
+ # If key and certs are not defined and localhost gem is required.
233
+ # localhost gem will be used for self signed
234
+ # Load localhost authority if not loaded.
235
+ ctx = localhost_authority && localhost_authority_context if params.empty?
236
+
237
+ ctx ||= MiniSSL::ContextBuilder.new(params, @events).context
228
238
 
229
239
  if fd = @inherited_fds.delete(str)
230
240
  logger.log "* Inherited #{str}"
@@ -282,6 +292,22 @@ module Puma
282
292
  end
283
293
  end
284
294
 
295
+ def localhost_authority
296
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
297
+ end
298
+
299
+ def localhost_authority_context
300
+ return unless localhost_authority
301
+
302
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
303
+ [localhost_authority.key_path, localhost_authority.certificate_path]
304
+ else
305
+ local_certificates_path = File.expand_path("~/.localhost")
306
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
307
+ end
308
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context
309
+ end
310
+
285
311
  # Tell the server to listen on host +host+, port +port+.
286
312
  # If +optimize_for_latency+ is true (the default) then clients connecting
287
313
  # will be optimized for latency over throughput.
@@ -299,6 +325,7 @@ module Puma
299
325
 
300
326
  host = host[1..-2] if host and host[0..0] == '['
301
327
  tcp_server = TCPServer.new(host, port)
328
+
302
329
  if optimize_for_latency
303
330
  tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
304
331
  end
@@ -320,6 +347,8 @@ module Puma
320
347
  optimize_for_latency=true, backlog=1024)
321
348
 
322
349
  raise "Puma compiled without SSL support" unless HAS_SSL
350
+ # Puma will try to use local authority context if context is supplied nil
351
+ ctx ||= localhost_authority_context
323
352
 
324
353
  if host == "localhost"
325
354
  loopback_addresses.each do |addr|
@@ -347,6 +376,8 @@ module Puma
347
376
 
348
377
  def inherit_ssl_listener(fd, ctx)
349
378
  raise "Puma compiled without SSL support" unless HAS_SSL
379
+ # Puma will try to use local authority context if context is supplied nil
380
+ ctx ||= localhost_authority_context
350
381
 
351
382
  s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
352
383
 
data/lib/puma/cli.rb CHANGED
@@ -112,6 +112,11 @@ module Puma
112
112
  file_config.load arg
113
113
  end
114
114
 
115
+ # Identical to supplying --config "-", but more semantic
116
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
117
+ file_config.load "-"
118
+ end
119
+
115
120
  o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
116
121
  configure_control_url(arg)
117
122
  end
data/lib/puma/client.rb CHANGED
@@ -56,6 +56,7 @@ module Puma
56
56
  @parser = HttpParser.new
57
57
  @parsed_bytes = 0
58
58
  @read_header = true
59
+ @read_proxy = false
59
60
  @ready = false
60
61
 
61
62
  @body = nil
@@ -69,7 +70,9 @@ module Puma
69
70
  @hijacked = false
70
71
 
71
72
  @peerip = nil
73
+ @listener = nil
72
74
  @remote_addr_header = nil
75
+ @expect_proxy_proto = false
73
76
 
74
77
  @body_remain = 0
75
78
 
@@ -81,7 +84,7 @@ module Puma
81
84
 
82
85
  attr_writer :peerip
83
86
 
84
- attr_accessor :remote_addr_header
87
+ attr_accessor :remote_addr_header, :listener
85
88
 
86
89
  def_delegators :@io, :closed?
87
90
 
@@ -105,7 +108,7 @@ module Puma
105
108
 
106
109
  # @!attribute [r] in_data_phase
107
110
  def in_data_phase
108
- !@read_header
111
+ !(@read_header || @read_proxy)
109
112
  end
110
113
 
111
114
  def set_timeout(val)
@@ -120,6 +123,7 @@ module Puma
120
123
  def reset(fast_check=true)
121
124
  @parser.reset
122
125
  @read_header = true
126
+ @read_proxy = !!@expect_proxy_proto
123
127
  @env = @proto_env.dup
124
128
  @body = nil
125
129
  @tempfile = nil
@@ -130,6 +134,8 @@ module Puma
130
134
  @in_last_chunk = false
131
135
 
132
136
  if @buffer
137
+ return false unless try_to_parse_proxy_protocol
138
+
133
139
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
134
140
 
135
141
  if @parser.finished?
@@ -142,8 +148,7 @@ module Puma
142
148
  return false
143
149
  else
144
150
  begin
145
- if fast_check &&
146
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
151
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
147
152
  return try_to_finish
148
153
  end
149
154
  rescue IOError
@@ -157,12 +162,36 @@ module Puma
157
162
  begin
158
163
  @io.close
159
164
  rescue IOError
160
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
165
+ Puma::Util.purge_interrupt_queue
166
+ end
167
+ end
168
+
169
+ # If necessary, read the PROXY protocol from the buffer. Returns
170
+ # false if more data is needed.
171
+ def try_to_parse_proxy_protocol
172
+ if @read_proxy
173
+ if @expect_proxy_proto == :v1
174
+ if @buffer.include? "\r\n"
175
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
176
+ if md[1]
177
+ @peerip = md[1].split(" ")[0]
178
+ end
179
+ @buffer = md.post_match
180
+ end
181
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
182
+ # request, this is just HTTP from a non-PROXY client; move on
183
+ @read_proxy = false
184
+ return @buffer.size > 0
185
+ else
186
+ return false
187
+ end
188
+ end
161
189
  end
190
+ true
162
191
  end
163
192
 
164
193
  def try_to_finish
165
- return read_body unless @read_header
194
+ return read_body if in_data_phase
166
195
 
167
196
  begin
168
197
  data = @io.read_nonblock(CHUNK_SIZE)
@@ -187,6 +216,8 @@ module Puma
187
216
  @buffer = data
188
217
  end
189
218
 
219
+ return false unless try_to_parse_proxy_protocol
220
+
190
221
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
191
222
 
192
223
  if @parser.finished?
@@ -201,13 +232,13 @@ module Puma
201
232
 
202
233
  def eagerly_finish
203
234
  return true if @ready
204
- return false unless IO.select([@to_io], nil, nil, 0)
235
+ return false unless @to_io.wait_readable(0)
205
236
  try_to_finish
206
237
  end
207
238
 
208
239
  def finish(timeout)
209
240
  return if @ready
210
- IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
241
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
211
242
  end
212
243
 
213
244
  def timeout!
@@ -243,6 +274,17 @@ module Puma
243
274
  @parsed_bytes == 0
244
275
  end
245
276
 
277
+ def expect_proxy_proto=(val)
278
+ if val
279
+ if @read_header
280
+ @read_proxy = true
281
+ end
282
+ else
283
+ @read_proxy = false
284
+ end
285
+ @expect_proxy_proto = val
286
+ end
287
+
246
288
  private
247
289
 
248
290
  def setup_body
@@ -308,7 +350,7 @@ module Puma
308
350
 
309
351
  @body_remain = remain
310
352
 
311
- return false
353
+ false
312
354
  end
313
355
 
314
356
  def read_body
@@ -33,9 +33,9 @@ module Puma
33
33
  Signal.trap "SIGINT", "IGNORE"
34
34
  Signal.trap "SIGCHLD", "DEFAULT"
35
35
 
36
- Thread.new do
36
+ Thread.new do
37
37
  Puma.set_thread_name "worker check pipe"
38
- IO.select [@check_pipe]
38
+ @check_pipe.wait_readable
39
39
  log "! Detected parent died, dying"
40
40
  exit! 1
41
41
  end
@@ -54,7 +54,14 @@ module Puma
54
54
  # things in shape before booting the app.
55
55
  @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
56
56
 
57
+ begin
57
58
  server = @server ||= start_server
59
+ rescue Exception => e
60
+ log "! Unable to start worker"
61
+ log e.backtrace[0]
62
+ exit 1
63
+ end
64
+
58
65
  restart_server = Queue.new << true << false
59
66
 
60
67
  fork_worker = @options[:fork_worker] && index == 0
@@ -99,7 +106,7 @@ module Puma
99
106
  begin
100
107
  @worker_write << "b#{Process.pid}:#{index}\n"
101
108
  rescue SystemCallError, IOError
102
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
109
+ Puma::Util.purge_interrupt_queue
103
110
  STDERR.puts "Master seems to have exited, exiting."
104
111
  return
105
112
  end
@@ -120,7 +127,7 @@ module Puma
120
127
  payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
121
128
  io << payload
122
129
  rescue IOError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
130
+ Puma::Util.purge_interrupt_queue
124
131
  break
125
132
  end
126
133
  sleep Const::WORKER_CHECK_INTERVAL
@@ -161,16 +168,6 @@ module Puma
161
168
  @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
162
169
  pid
163
170
  end
164
-
165
- def wakeup!
166
- return unless @wakeup
167
-
168
- begin
169
- @wakeup.write "!" unless @wakeup.closed?
170
- rescue SystemCallError, IOError
171
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
- end
173
- end
174
171
  end
175
172
  end
176
173
  end
data/lib/puma/cluster.rb CHANGED
@@ -164,16 +164,6 @@ module Puma
164
164
  ].compact.min
165
165
  end
166
166
 
167
- def wakeup!
168
- return unless @wakeup
169
-
170
- begin
171
- @wakeup.write "!" unless @wakeup.closed?
172
- rescue SystemCallError, IOError
173
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
174
- end
175
- end
176
-
177
167
  def worker(index, master)
178
168
  @workers = []
179
169
 
@@ -426,9 +416,7 @@ module Puma
426
416
 
427
417
  check_workers
428
418
 
429
- res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
430
-
431
- if res
419
+ if read.wait_readable([0, @next_check - Time.now].max)
432
420
  req = read.read_nonblock(1)
433
421
 
434
422
  @next_check = Time.now if req == "!"
@@ -200,7 +200,7 @@ module Puma
200
200
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
201
201
  :remote_address => :socket,
202
202
  :tag => method(:infer_tag),
203
- :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
203
+ :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
204
204
  :rackup => DefaultRackup,
205
205
  :logger => STDOUT,
206
206
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
@@ -343,6 +343,8 @@ module Puma
343
343
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
344
344
 
345
345
  rack_app, rack_options = rack_builder.parse_file(rackup)
346
+ rack_options = rack_options || {}
347
+
346
348
  @options.file_options.merge!(rack_options)
347
349
 
348
350
  config_ru_binds = []
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.3.1".freeze
104
- CODE_NAME = "Sweetnighter".freeze
103
+ PUMA_VERSION = VERSION = "5.5.1".freeze
104
+ CODE_NAME = "Zawgyi".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -247,5 +247,7 @@ module Puma
247
247
 
248
248
  # Banned keys of response header
249
249
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
250
+
251
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
250
252
  end
251
253
  end
@@ -47,7 +47,7 @@ module Puma
47
47
  @control_auth_token = nil
48
48
  @config_file = nil
49
49
  @command = nil
50
- @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
51
51
 
52
52
  @argv = argv.dup
53
53
  @stdout = stdout
data/lib/puma/dsl.rb CHANGED
@@ -201,7 +201,7 @@ module Puma
201
201
  # * Set the socket backlog depth with +backlog+, default is 1024.
202
202
  # * Set up an SSL certificate with +key+ & +cert+.
203
203
  # * Set whether to optimize for low latency instead of throughput with
204
- # +low_latency+, default is to optimize for low latency. This is done
204
+ # +low_latency+, default is to not optimize for low latency. This is done
205
205
  # via +Socket::TCP_NODELAY+.
206
206
  # * Set socket permissions with +umask+.
207
207
  #
@@ -381,6 +381,13 @@ module Puma
381
381
  @options[:rackup] ||= path.to_s
382
382
  end
383
383
 
384
+ # Allows setting `env['rack.url_scheme']`.
385
+ # Only necessary if X-Forwarded-Proto is not being set by your proxy
386
+ # Normal values are 'http' or 'https'.
387
+ def rack_url_scheme(scheme=nil)
388
+ @options[:rack_url_scheme] = scheme
389
+ end
390
+
384
391
  def early_hints(answer=true)
385
392
  @options[:early_hints] = answer
386
393
  end
@@ -578,7 +585,7 @@ module Puma
578
585
  # end
579
586
  def after_worker_fork(&block)
580
587
  @options[:after_worker_fork] ||= []
581
- @options[:after_worker_fork] = block
588
+ @options[:after_worker_fork] << block
582
589
  end
583
590
 
584
591
  alias_method :after_worker_boot, :after_worker_fork
@@ -811,7 +818,7 @@ module Puma
811
818
  # a kernel syscall is required which for very fast rack handlers
812
819
  # slows down the handling significantly.
813
820
  #
814
- # There are 4 possible values:
821
+ # There are 5 possible values:
815
822
  #
816
823
  # 1. **:socket** (the default) - read the peername from the socket using the
817
824
  # syscall. This is the normal behavior.
@@ -821,7 +828,10 @@ module Puma
821
828
  # `set_remote_address header: "X-Real-IP"`.
822
829
  # Only the first word (as separated by spaces or comma) is used, allowing
823
830
  # headers such as X-Forwarded-For to be used as well.
824
- # 4. **\<Any string\>** - this allows you to hardcode remote address to any value
831
+ # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
832
+ # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
833
+ # protocol attached to it, will fall back to :socket
834
+ # 5. **\<Any string\>** - this allows you to hardcode remote address to any value
825
835
  # you wish. Because Puma never uses this field anyway, it's format is
826
836
  # entirely in your hands.
827
837
  #
@@ -839,6 +849,13 @@ module Puma
839
849
  if hdr = val[:header]
840
850
  @options[:remote_address] = :header
841
851
  @options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
852
+ elsif protocol_version = val[:proxy_protocol]
853
+ @options[:remote_address] = :proxy_protocol
854
+ protocol_version = protocol_version.downcase.to_sym
855
+ unless [:v1].include?(protocol_version)
856
+ raise "Invalid value for proxy_protocol - #{protocol_version.inspect}"
857
+ end
858
+ @options[:remote_address_proxy_protocol] = protocol_version
842
859
  else
843
860
  raise "Invalid value for set_remote_address - #{val.inspect}"
844
861
  end
@@ -17,7 +17,7 @@ module Puma
17
17
  # be particularly full-featured or fast. It just has to handle the few places
18
18
  # where Puma relies on JSON serialization internally.
19
19
 
20
- module JSON
20
+ module JSONSerialization
21
21
  QUOTE = /"/
22
22
  BACKSLASH = /\\/
23
23
  CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
data/lib/puma/launcher.rb CHANGED
@@ -319,10 +319,12 @@ module Puma
319
319
  log '* Pruning Bundler environment'
320
320
  home = ENV['GEM_HOME']
321
321
  bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
322
+ bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
322
323
  with_unbundled_env do
323
324
  ENV['GEM_HOME'] = home
324
325
  ENV['BUNDLE_GEMFILE'] = bundle_gemfile
325
326
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
327
+ ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
326
328
  args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
327
329
  # Ruby 2.0+ defaults to true which breaks socket activation
328
330
  args += [{:close_others => false}]
data/lib/puma/minissl.rb CHANGED
@@ -161,30 +161,15 @@ module Puma
161
161
  @socket.flush
162
162
  end
163
163
 
164
- def read_and_drop(timeout = 1)
165
- return :timeout unless IO.select([@socket], nil, nil, timeout)
166
- case @socket.read_nonblock(1024, exception: false)
167
- when nil
168
- :eof
169
- when :wait_readable
170
- :eagain
171
- else
172
- :drop
173
- end
174
- end
175
-
176
- def should_drop_bytes?
177
- @engine.init? || !@engine.shutdown
178
- end
179
-
180
164
  def close
181
165
  begin
182
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
183
- # Don't let this socket hold this loop forever.
184
- # If it can't send more packets within 1s, then give up.
185
- return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
166
+ unless @engine.shutdown
167
+ while alert_data = @engine.extract
168
+ @socket.write alert_data
169
+ end
170
+ end
186
171
  rescue IOError, SystemCallError
187
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
+ Puma::Util.purge_interrupt_queue
188
173
  # nothing
189
174
  ensure
190
175
  @socket.close
data/lib/puma/plugin.rb CHANGED
@@ -91,7 +91,7 @@ module Puma
91
91
  path = ary.first[CALLER_FILE]
92
92
 
93
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
- return m[1]
94
+ m[1]
95
95
  end
96
96
 
97
97
  def self.create(&blk)
Binary file
@@ -165,7 +165,7 @@ module Puma::Rack
165
165
  require config
166
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
167
167
  end
168
- return app, options
168
+ [app, options]
169
169
  end
170
170
 
171
171
  def self.new_from_string(builder_script, file="(rackup)")
data/lib/puma/request.rb CHANGED
@@ -26,9 +26,10 @@ module Puma
26
26
  # Finally, it'll return +true+ on keep-alive connections.
27
27
  # @param client [Puma::Client]
28
28
  # @param lines [Puma::IOBuffer]
29
+ # @param requests [Integer]
29
30
  # @return [Boolean,:async]
30
31
  #
31
- def handle_request(client, lines)
32
+ def handle_request(client, lines, requests)
32
33
  env = client.env
33
34
  io = client.io # io may be a MiniSSL::Socket
34
35
 
@@ -50,7 +51,7 @@ module Puma
50
51
  head = env[REQUEST_METHOD] == HEAD
51
52
 
52
53
  env[RACK_INPUT] = body
53
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
+ env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
55
 
55
56
  if @early_hints
56
57
  env[EARLY_HINTS] = lambda { |headers|
@@ -110,7 +111,7 @@ module Puma
110
111
 
111
112
  cork_socket io
112
113
 
113
- str_headers(env, status, headers, res_info, lines)
114
+ str_headers(env, status, headers, res_info, lines, requests, client)
114
115
 
115
116
  line_ending = LINE_END
116
117
 
@@ -175,7 +176,7 @@ module Puma
175
176
  after_reply.each { |o| o.call }
176
177
  end
177
178
 
178
- return res_info[:keep_alive]
179
+ res_info[:keep_alive]
179
180
  end
180
181
 
181
182
  # @param env [Hash] see Puma::Client#env, from request
@@ -200,7 +201,7 @@ module Puma
200
201
  begin
201
202
  n = io.syswrite str
202
203
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
203
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
204
+ unless io.wait_writable WRITE_TIMEOUT
204
205
  raise ConnectionError, "Socket timeout writing data"
205
206
  end
206
207
 
@@ -367,9 +368,11 @@ module Puma
367
368
  # @param headers [Hash] the headers returned by the Rack application
368
369
  # @param res_info [Hash] used to pass info between this method and #handle_request
369
370
  # @param lines [Puma::IOBuffer] modified inn place
371
+ # @param requests [Integer] number of inline requests handled
372
+ # @param client [Puma::Client]
370
373
  # @version 5.0.3
371
374
  #
372
- def str_headers(env, status, headers, res_info, lines)
375
+ def str_headers(env, status, headers, res_info, lines, requests, client)
373
376
  line_ending = LINE_END
374
377
  colon = COLON
375
378
 
@@ -410,6 +413,14 @@ module Puma
410
413
  # if running without request queueing
411
414
  res_info[:keep_alive] &&= @queue_requests
412
415
 
416
+ # Close the connection after a reasonable number of inline requests
417
+ # if the server is at capacity and the listener has a new connection ready.
418
+ # This allows Puma to service connections fairly when the number
419
+ # of concurrent connections exceeds the size of the threadpool.
420
+ res_info[:keep_alive] &&= requests < @max_fast_inline ||
421
+ @thread_pool.busy_threads < @max_threads ||
422
+ !client.listener.to_io.wait_readable(0)
423
+
413
424
  res_info[:response_hijack] = nil
414
425
 
415
426
  headers.each do |k, vs|
data/lib/puma/runner.rb CHANGED
@@ -15,6 +15,16 @@ module Puma
15
15
  @app = nil
16
16
  @control = nil
17
17
  @started_at = Time.now
18
+ @wakeup = nil
19
+ end
20
+
21
+ def wakeup!
22
+ return unless @wakeup
23
+
24
+ @wakeup.write "!" unless @wakeup.closed?
25
+
26
+ rescue SystemCallError, IOError
27
+ Puma::Util.purge_interrupt_queue
18
28
  end
19
29
 
20
30
  def development?
@@ -108,9 +118,7 @@ module Puma
108
118
  append = @options[:redirect_append]
109
119
 
110
120
  if stdout
111
- unless Dir.exist?(File.dirname(stdout))
112
- raise "Cannot redirect STDOUT to #{stdout}"
113
- end
121
+ ensure_output_directory_exists(stdout, 'STDOUT')
114
122
 
115
123
  STDOUT.reopen stdout, (append ? "a" : "w")
116
124
  STDOUT.puts "=== puma startup: #{Time.now} ==="
@@ -118,9 +126,7 @@ module Puma
118
126
  end
119
127
 
120
128
  if stderr
121
- unless Dir.exist?(File.dirname(stderr))
122
- raise "Cannot redirect STDERR to #{stderr}"
123
- end
129
+ ensure_output_directory_exists(stderr, 'STDERR')
124
130
 
125
131
  STDERR.reopen stderr, (append ? "a" : "w")
126
132
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -159,5 +165,12 @@ module Puma
159
165
  server.inherit_binder @launcher.binder
160
166
  server
161
167
  end
168
+
169
+ private
170
+ def ensure_output_directory_exists(path, io_name)
171
+ unless Dir.exist?(File.dirname(path))
172
+ raise "Cannot redirect #{io_name} to #{path}"
173
+ end
174
+ end
162
175
  end
163
176
  end