Skip to content

Commit

Permalink
Allow backlog parameter to be set with ssl_bind DSL (puma#2780)
Browse files Browse the repository at this point in the history
Hint about backlog upper limit due to net.core.somaxconn
Guard against invalid backlog value
  • Loading branch information
dalibor authored and JuanitoFatas committed Sep 9, 2022
1 parent 2505955 commit eb0dc7b
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 10 deletions.
8 changes: 4 additions & 4 deletions docs/architecture.md
Expand Up @@ -31,10 +31,10 @@ _workers_, and we sometimes call the threads created by Puma's
![https://bit.ly/2zwzhEK](images/puma-connection-flow.png)

* Upon startup, Puma listens on a TCP or UNIX socket.
* The backlog of this socket is configured (with a default of 1024). The
backlog determines the size of the queue for unaccepted connections.
Generally, you'll never hit the backlog cap in production. If the backlog is
full, the operating system refuses new connections.
* The backlog of this socket is configured with a default of 1024, but the
actual backlog value is capped by the `net.core.somaxconn` sysctl value.
The backlog determines the size of the queue for unaccepted connections. If
the backlog is full, the operating system is not accepting new connections.
* This socket backlog is distinct from the `backlog` of work as reported by
`Puma.stats` or the control server. The backlog that `Puma.stats` refers to
represents the number of connections in the process' `todo` set waiting for
Expand Down
7 changes: 4 additions & 3 deletions lib/puma/binder.rb
Expand Up @@ -168,9 +168,9 @@ def parse(binds, logger, log_msg = 'Listening')
params = Util.parse_query uri.query

opt = params.key?('low_latency') && params['low_latency'] != 'false'
bak = params.fetch('backlog', 1024).to_i
backlog = params.fetch('backlog', 1024).to_i

io = add_tcp_listener uri.host, uri.port, opt, bak
io = add_tcp_listener uri.host, uri.port, opt, backlog

@ios[ios_len..-1].each do |i|
addr = loc_addr_str i
Expand Down Expand Up @@ -255,7 +255,8 @@ def parse(binds, logger, log_msg = 'Listening')
logger.log "* Activated #{str}"
else
ios_len = @ios.length
io = add_ssl_listener uri.host, uri.port, ctx
backlog = params.fetch('backlog', 1024).to_i
io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog

@ios[ios_len..-1].each do |i|
addr = loc_addr_str i
Expand Down
6 changes: 4 additions & 2 deletions lib/puma/dsl.rb
Expand Up @@ -48,14 +48,16 @@ def self.ssl_bind_str(host, port, opts)

ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)

backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''

if defined?(JRUBY_VERSION)
ssl_cipher_list = opts[:ssl_cipher_list] ?
"&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil

keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"

"ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
"&verify_mode=#{verify}#{tls_str}#{ca_additions}"
"&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
else
ssl_cipher_filter = opts[:ssl_cipher_filter] ?
"&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
Expand All @@ -64,7 +66,7 @@ def self.ssl_bind_str(host, port, opts)
"&verification_flags=#{Array(ary).join ','}" : nil

"ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
end
end

Expand Down
18 changes: 18 additions & 0 deletions test/test_binder.rb
Expand Up @@ -275,6 +275,24 @@ def test_env_contains_stderr
assert_equal @events.stderr, env_hash["rack.errors"]
end

def test_ssl_binder_sets_backlog
skip_unless :ssl

host = '127.0.0.1'
port = UniquePort.call
tcp_server = TCPServer.new(host, port)
tcp_server.define_singleton_method(:listen) do |backlog|
Thread.current[:backlog] = backlog
super(backlog)
end

TCPServer.stub(:new, tcp_server) do
@binder.parse ["ssl://#{host}:#{port}?#{ssl_query}&backlog=2048"], @events
end

assert_equal 2048, Thread.current[:backlog]
end

def test_close_calls_close_on_ios
@mocked_ios = [Minitest::Mock.new, Minitest::Mock.new]
@mocked_ios.each { |m| m.expect(:close, true) }
Expand Down
15 changes: 15 additions & 0 deletions test/test_config.rb
Expand Up @@ -98,6 +98,21 @@ def test_ssl_bind_with_cert_and_key_pem
assert_equal [ssl_binding], conf.options[:binds]
end

def test_ssl_bind_with_backlog
skip_unless :ssl

conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
backlog: "2048",
}
end

conf.load

ssl_binding = conf.options[:binds].first
assert ssl_binding.include?('&backlog=2048')
end

def test_ssl_bind_jruby
skip_unless :jruby
skip_unless :ssl
Expand Down
2 changes: 1 addition & 1 deletion test/test_thread_pool.rb
Expand Up @@ -5,7 +5,7 @@
class TestThreadPool < Minitest::Test

def teardown
@pool.shutdown(1) if @pool
@pool.shutdown(1) if defined?(@pool)
end

def new_pool(min, max, &block)
Expand Down

0 comments on commit eb0dc7b

Please sign in to comment.