mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
init: account for maximum file descriptors needed by HTTP
This commit is contained in:
@@ -31,18 +31,6 @@ If you front `bitcoind` with a reverse proxy or CDN such as Caddy or nginx with
|
||||
the headers-more module, you can override these defaults there. Keep overrides
|
||||
scoped to responses you know are safe to cache more aggressively.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
There is a known issue in the REST interface that can cause a node to crash if
|
||||
too many http connections are being opened at the same time because the system runs
|
||||
out of available file descriptors. To prevent this from happening you might
|
||||
want to increase the number of maximum allowed file descriptors in your system
|
||||
and try to prevent opening too many connections to your rest interface at the
|
||||
same time if this is under your control. It is hard to give general advice
|
||||
since this depends on your system but if you make several hundred requests at
|
||||
once you are definitely at risk of encountering this issue.
|
||||
|
||||
Supported API
|
||||
-------------
|
||||
|
||||
|
||||
@@ -16,4 +16,6 @@ Certain HTTP edge cases will observe different behavior to be more RFC-compliant
|
||||
- Multiple "Content-Length" headers with different values are rejected
|
||||
|
||||
A new configuration option `-rpcmaxconnections` (default `16`) limits the
|
||||
number of simultaneously connected HTTP clients to the server.
|
||||
number of simultaneously connected HTTP clients to the server. The application
|
||||
will now attempt to reserve file descriptors for the HTTP server sockets. If your
|
||||
system has limited resources, consider using a lower setting.
|
||||
|
||||
49
src/init.cpp
49
src/init.cpp
@@ -114,6 +114,7 @@
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
@@ -1075,18 +1076,58 @@ bool AppInitParameterInteraction(const ArgsManager& args)
|
||||
const size_t max_private{args.GetBoolArg("-privatebroadcast", DEFAULT_PRIVATE_BROADCAST)
|
||||
? MAX_PRIVATE_BROADCAST_CONNECTIONS
|
||||
: 0};
|
||||
// Reserve enough FDs to account for the bare minimum, plus any manual connections, plus the bound interfaces
|
||||
int min_required_fds = MIN_CORE_FDS + MAX_ADDNODE_CONNECTIONS + num_p2p_bind;
|
||||
|
||||
// Try raising the FD limit to what we need (available_fds may be smaller than the requested amount if this fails)
|
||||
available_fds = RaiseFileDescriptorLimit(user_p2p_max_connections + max_private + min_required_fds);
|
||||
// HTTP server listen sockets: by default two (IPv4 and IPv6 loopback), or one per -rpcbind entry
|
||||
int num_rpc_bind = std::max(args.GetArgs("-rpcbind").size(), size_t(2));
|
||||
// HTTP server connected client sockets
|
||||
int user_rpc_max_connections = args.GetArg<int>("-rpcmaxconnections", DEFAULT_MAX_HTTP_CONNECTIONS);
|
||||
if (user_rpc_max_connections < 1) {
|
||||
return InitError(Untranslated("-rpcmaxconnections must be greater than zero. Use -server=0 to disable HTTP."));
|
||||
}
|
||||
|
||||
// Reserve enough FDs to account for the bare minimum, plus any manual connections, plus the bound interfaces.
|
||||
// Every element is an int >= 0 so summing in int64_t cannot overflow.
|
||||
// RaiseFileDescriptorLimit() accepts an int so we check that limit before casting.
|
||||
const int64_t total_fds = int64_t{MIN_CORE_FDS} +
|
||||
MAX_ADDNODE_CONNECTIONS +
|
||||
num_p2p_bind +
|
||||
num_rpc_bind +
|
||||
user_rpc_max_connections +
|
||||
user_p2p_max_connections +
|
||||
static_cast<int64_t>(max_private);
|
||||
if (total_fds > std::numeric_limits<int>::max()) {
|
||||
return InitError(Untranslated("Too many file descriptors requested. Try lower values for -rpcmaxconnections "
|
||||
"or -maxconnections, or fewer settings of "
|
||||
"-rpcbind, -bind and -whitebind"));
|
||||
}
|
||||
|
||||
// Subset of total_fds must also be a safe int
|
||||
int min_required_fds = MIN_CORE_FDS +
|
||||
MAX_ADDNODE_CONNECTIONS +
|
||||
num_p2p_bind +
|
||||
num_rpc_bind +
|
||||
user_rpc_max_connections;
|
||||
|
||||
// Try raising the FD limit to what the user wants (available_fds may be smaller than the requested amount if this fails)
|
||||
available_fds = RaiseFileDescriptorLimit(static_cast<int>(total_fds));
|
||||
// If we are using select instead of poll, our actual limit may be even smaller
|
||||
#ifndef USE_POLL
|
||||
available_fds = std::min(FD_SETSIZE, available_fds);
|
||||
#endif
|
||||
// The system can't support our bare minimum
|
||||
if (available_fds < min_required_fds)
|
||||
return InitError(strprintf(_("Not enough file descriptors available. %d available, %d required."), available_fds, min_required_fds));
|
||||
|
||||
// The system can support our minimum but not the full amount the user requested.
|
||||
if (available_fds < total_fds) {
|
||||
// If the user is requesting extra HTTP connections, abort. They need to change that.
|
||||
if (user_rpc_max_connections > DEFAULT_MAX_HTTP_CONNECTIONS) {
|
||||
return InitError(strprintf(_("Not enough file descriptors available. "
|
||||
"Try reducing -rpcmaxconnections or using the default value of %d"),
|
||||
DEFAULT_MAX_HTTP_CONNECTIONS));
|
||||
}
|
||||
}
|
||||
|
||||
// Trim requested connection counts, to fit into system limitations
|
||||
num_p2p_max_connections = std::min(available_fds - min_required_fds, user_p2p_max_connections);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
@@ -362,6 +363,51 @@ class InitTest(BitcoinTestFramework):
|
||||
self.log.info("Testing node startup with fd limit above INT_MAX")
|
||||
self.restart_node_with_fd_limit(1 << 31)
|
||||
|
||||
def init_fd_overflow_test(self):
|
||||
node = self.nodes[1]
|
||||
if node.running:
|
||||
self.stop_node(1)
|
||||
|
||||
# A value larger than any possible int saturates to INT_MAX during arg parsing.
|
||||
# Adding in other file descriptor requirements is guaranteed to overflow,
|
||||
# so expect an InitError before RaiseFileDescriptorLimit() is called.
|
||||
self.log.info("Checking -rpcmaxconnections setting that would overflow int is rejected")
|
||||
node.assert_start_raises_init_error(
|
||||
extra_args=[f"-rpcmaxconnections={2**64}"],
|
||||
expected_msg="Error: Too many file descriptors requested.",
|
||||
match=ErrorMatch.PARTIAL_REGEX
|
||||
)
|
||||
|
||||
if self.RLIM_INFINITY is not None:
|
||||
# Get the platform's file descriptor limit, if possible
|
||||
import resource
|
||||
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
# Lower the hard limit so RaiseFileDescriptorLimit() has a ceiling.
|
||||
# The hard limit can not be raised again without root privilges,
|
||||
# so this test should always be left for last in the process.
|
||||
try:
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, soft))
|
||||
except (ValueError, OSError):
|
||||
self.log.info(f"Skipping rlimit test: cannot reduce hard limit (soft={soft}, hard={hard})")
|
||||
return
|
||||
|
||||
self.log.info("Checking that large -maxconnections setting gets adjusted for available file descriptors")
|
||||
# Note this prints a message to the log and stderr but does not abort the process
|
||||
with node.assert_debug_log(expected_msgs=[f"Reducing -maxconnections from {soft} "]):
|
||||
self.restart_node(1, extra_args=[f"-maxconnections={soft}"])
|
||||
self.stop_node(1, expected_stderr=re.compile(fr"Reducing -maxconnections from {soft} "))
|
||||
|
||||
# From httpserver.h
|
||||
DEFAULT_MAX_HTTP_CONNECTIONS = 16
|
||||
|
||||
self.log.info("Checking -rpcmaxconnections gets blamed if available file descriptors are insufficient")
|
||||
node.assert_start_raises_init_error(
|
||||
extra_args=[f"-rpcmaxconnections={DEFAULT_MAX_HTTP_CONNECTIONS + 1}", f"-maxconnections={soft}"],
|
||||
expected_msg="Not enough file descriptors available. Try reducing -rpcmaxconnections",
|
||||
match=ErrorMatch.PARTIAL_REGEX
|
||||
)
|
||||
|
||||
def run_test(self):
|
||||
self.init_pid_test()
|
||||
self.init_stress_test_interrupt()
|
||||
@@ -370,6 +416,7 @@ class InitTest(BitcoinTestFramework):
|
||||
self.init_empty_test()
|
||||
self.init_rlimit_test()
|
||||
self.init_rlimit_large_test()
|
||||
self.init_fd_overflow_test()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user