mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-11-29 06:40:59 +01:00
REORG: haproxy: move limits handlers to limits
This patch moves handlers to compute process related limits in 'limits' compilation unit.
This commit is contained in:
parent
22db643648
commit
1f8addfdc2
196
src/haproxy.c
196
src/haproxy.c
@ -1438,202 +1438,6 @@ static void ha_random_boot(char *const *argv)
|
||||
ha_random_seed(boot_seed, sizeof(boot_seed));
|
||||
}
|
||||
|
||||
/* considers splicing proxies' maxconn, computes the ideal global.maxpipes
|
||||
* setting, and returns it. It may return -1 meaning "unlimited" if some
|
||||
* unlimited proxies have been found and the global.maxconn value is not yet
|
||||
* set. It may also return a value greater than maxconn if it's not yet set.
|
||||
* Note that a value of zero means there is no need for pipes. -1 is never
|
||||
* returned if global.maxconn is valid.
|
||||
*/
|
||||
int compute_ideal_maxpipes()
|
||||
{
|
||||
struct proxy *cur;
|
||||
int nbfe = 0, nbbe = 0;
|
||||
int unlimited = 0;
|
||||
int pipes;
|
||||
int max;
|
||||
|
||||
for (cur = proxies_list; cur; cur = cur->next) {
|
||||
if (cur->options2 & (PR_O2_SPLIC_ANY)) {
|
||||
if (cur->cap & PR_CAP_FE) {
|
||||
max = cur->maxconn;
|
||||
nbfe += max;
|
||||
if (!max) {
|
||||
unlimited = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cur->cap & PR_CAP_BE) {
|
||||
max = cur->fullconn ? cur->fullconn : global.maxconn;
|
||||
nbbe += max;
|
||||
if (!max) {
|
||||
unlimited = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipes = MAX(nbfe, nbbe);
|
||||
if (global.maxconn) {
|
||||
if (pipes > global.maxconn || unlimited)
|
||||
pipes = global.maxconn;
|
||||
} else if (unlimited) {
|
||||
pipes = -1;
|
||||
}
|
||||
|
||||
return pipes >= 4 ? pipes / 4 : pipes;
|
||||
}
|
||||
|
||||
/* considers global.maxsocks, global.maxpipes, async engines, SSL frontends and
|
||||
* rlimits and computes an ideal maxconn. It's meant to be called only when
|
||||
* maxsock contains the sum of listening FDs, before it is updated based on
|
||||
* maxconn and pipes. If there are not enough FDs left, DEFAULT_MAXCONN (by
|
||||
* default 100) is returned as it is expected that it will even run on tight
|
||||
* environments, and will maintain compatibility with previous packages that
|
||||
* used to rely on this value as the default one. The system will emit a
|
||||
* warning indicating how many FDs are missing anyway if needed.
|
||||
*/
|
||||
int compute_ideal_maxconn()
|
||||
{
|
||||
int ssl_sides = !!global.ssl_used_frontend + !!global.ssl_used_backend;
|
||||
int engine_fds = global.ssl_used_async_engines * ssl_sides;
|
||||
int pipes = compute_ideal_maxpipes();
|
||||
int remain = MAX(rlim_fd_cur_at_boot, rlim_fd_max_at_boot);
|
||||
int maxconn;
|
||||
|
||||
/* we have to take into account these elements :
|
||||
* - number of engine_fds, which inflates the number of FD needed per
|
||||
* connection by this number.
|
||||
* - number of pipes per connection on average : for the unlimited
|
||||
* case, this is 0.5 pipe FDs per connection, otherwise it's a
|
||||
* fixed value of 2*pipes.
|
||||
* - two FDs per connection
|
||||
*/
|
||||
|
||||
/* on some modern distros for archs like amd64 fs.nr_open (kernel max)
|
||||
* could be in order of 1 billion. Systemd since the version 256~rc3-3
|
||||
* bumped fs.nr_open as the hard RLIMIT_NOFILE (rlim_fd_max_at_boot).
|
||||
* If we are started without any limits, we risk to finish with computed
|
||||
* maxconn = ~500000000, maxsock = ~2*maxconn. So, fdtab will be
|
||||
* extremely large and watchdog will kill the process, when it will try
|
||||
* to loop over the fdtab (see fd_reregister_all). Please note, that
|
||||
* fd_hard_limit is taken in account implicitly via 'ideal_maxconn'
|
||||
* value in all global.maxconn adjustements, when global.rlimit_memmax
|
||||
* is set:
|
||||
*
|
||||
* MIN(global.maxconn, capped by global.rlimit_memmax, ideal_maxconn);
|
||||
*
|
||||
* It also caps global.rlimit_nofile, if it couldn't be set as rlim_cur
|
||||
* and as rlim_max. So, fd_hard_limitit is a good parameter to serve as
|
||||
* a safeguard, when no haproxy-specific limits are set, i.e.
|
||||
* rlimit_memmax, maxconn, rlimit_nofile. But it must be kept as a zero,
|
||||
* if only one of these ha-specific limits is presented in config or in
|
||||
* the cmdline.
|
||||
*/
|
||||
if (!global.fd_hard_limit && !global.maxconn && !global.rlimit_nofile
|
||||
&& !global.rlimit_memmax)
|
||||
global.fd_hard_limit = DEFAULT_MAXFD;
|
||||
|
||||
if (remain > global.fd_hard_limit)
|
||||
remain = global.fd_hard_limit;
|
||||
|
||||
/* subtract listeners and checks */
|
||||
remain -= global.maxsock;
|
||||
|
||||
/* one epoll_fd/kqueue_fd per thread */
|
||||
remain -= global.nbthread;
|
||||
|
||||
/* one wake-up pipe (2 fd) per thread */
|
||||
remain -= 2 * global.nbthread;
|
||||
|
||||
/* Fixed pipes values : we only subtract them if they're not larger
|
||||
* than the remaining FDs because pipes are optional.
|
||||
*/
|
||||
if (pipes >= 0 && pipes * 2 < remain)
|
||||
remain -= pipes * 2;
|
||||
|
||||
if (pipes < 0) {
|
||||
/* maxsock = maxconn * 2 + maxconn/4 * 2 + maxconn * engine_fds.
|
||||
* = maxconn * (2 + 0.5 + engine_fds)
|
||||
* = maxconn * (4 + 1 + 2*engine_fds) / 2
|
||||
*/
|
||||
maxconn = 2 * remain / (5 + 2 * engine_fds);
|
||||
} else {
|
||||
/* maxsock = maxconn * 2 + maxconn * engine_fds.
|
||||
* = maxconn * (2 + engine_fds)
|
||||
*/
|
||||
maxconn = remain / (2 + engine_fds);
|
||||
}
|
||||
|
||||
return MAX(maxconn, DEFAULT_MAXCONN);
|
||||
}
|
||||
|
||||
/* computes the estimated maxsock value for the given maxconn based on the
|
||||
* possibly set global.maxpipes and existing partial global.maxsock. It may
|
||||
* temporarily change global.maxconn for the time needed to propagate the
|
||||
* computations, and will reset it.
|
||||
*/
|
||||
int compute_ideal_maxsock(int maxconn)
|
||||
{
|
||||
int maxpipes = global.maxpipes;
|
||||
int maxsock = global.maxsock;
|
||||
|
||||
|
||||
if (!maxpipes) {
|
||||
int old_maxconn = global.maxconn;
|
||||
|
||||
global.maxconn = maxconn;
|
||||
maxpipes = compute_ideal_maxpipes();
|
||||
global.maxconn = old_maxconn;
|
||||
}
|
||||
|
||||
maxsock += maxconn * 2; /* each connection needs two sockets */
|
||||
maxsock += maxpipes * 2; /* each pipe needs two FDs */
|
||||
maxsock += global.nbthread; /* one epoll_fd/kqueue_fd per thread */
|
||||
maxsock += 2 * global.nbthread; /* one wake-up pipe (2 fd) per thread */
|
||||
|
||||
/* compute fd used by async engines */
|
||||
if (global.ssl_used_async_engines) {
|
||||
int sides = !!global.ssl_used_frontend + !!global.ssl_used_backend;
|
||||
|
||||
maxsock += maxconn * sides * global.ssl_used_async_engines;
|
||||
}
|
||||
return maxsock;
|
||||
}
|
||||
|
||||
/* Tests if it is possible to set the current process's RLIMIT_NOFILE to
|
||||
* <maxsock>, then sets it back to the previous value. Returns non-zero if the
|
||||
* value is accepted, non-zero otherwise. This is used to determine if an
|
||||
* automatic limit may be applied or not. When it is not, the caller knows that
|
||||
* the highest we can do is the rlim_max at boot. In case of error, we return
|
||||
* that the setting is possible, so that we defer the error processing to the
|
||||
* final stage in charge of enforcing this.
|
||||
*/
|
||||
int check_if_maxsock_permitted(int maxsock)
|
||||
{
|
||||
struct rlimit orig_limit, test_limit;
|
||||
int ret;
|
||||
|
||||
if (global.fd_hard_limit && maxsock > global.fd_hard_limit)
|
||||
return 0;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &orig_limit) != 0)
|
||||
return 1;
|
||||
|
||||
/* don't go further if we can't even set to what we have */
|
||||
if (raise_rlim_nofile(NULL, &orig_limit) != 0)
|
||||
return 1;
|
||||
|
||||
test_limit.rlim_max = MAX(maxsock, orig_limit.rlim_max);
|
||||
test_limit.rlim_cur = test_limit.rlim_max;
|
||||
ret = raise_rlim_nofile(NULL, &test_limit);
|
||||
|
||||
if (raise_rlim_nofile(NULL, &orig_limit) != 0)
|
||||
return 1;
|
||||
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
/* Evaluates a condition provided within a conditional block of the
|
||||
* configuration. Makes process to exit with 0, if the condition is true, with
|
||||
|
||||
197
src/limits.c
197
src/limits.c
@ -40,3 +40,200 @@ int raise_rlim_nofile(struct rlimit *old_limit, struct rlimit *new_limit)
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* considers splicing proxies' maxconn, computes the ideal global.maxpipes
|
||||
* setting, and returns it. It may return -1 meaning "unlimited" if some
|
||||
* unlimited proxies have been found and the global.maxconn value is not yet
|
||||
* set. It may also return a value greater than maxconn if it's not yet set.
|
||||
* Note that a value of zero means there is no need for pipes. -1 is never
|
||||
* returned if global.maxconn is valid.
|
||||
*/
|
||||
int compute_ideal_maxpipes()
|
||||
{
|
||||
struct proxy *cur;
|
||||
int nbfe = 0, nbbe = 0;
|
||||
int unlimited = 0;
|
||||
int pipes;
|
||||
int max;
|
||||
|
||||
for (cur = proxies_list; cur; cur = cur->next) {
|
||||
if (cur->options2 & (PR_O2_SPLIC_ANY)) {
|
||||
if (cur->cap & PR_CAP_FE) {
|
||||
max = cur->maxconn;
|
||||
nbfe += max;
|
||||
if (!max) {
|
||||
unlimited = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cur->cap & PR_CAP_BE) {
|
||||
max = cur->fullconn ? cur->fullconn : global.maxconn;
|
||||
nbbe += max;
|
||||
if (!max) {
|
||||
unlimited = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipes = MAX(nbfe, nbbe);
|
||||
if (global.maxconn) {
|
||||
if (pipes > global.maxconn || unlimited)
|
||||
pipes = global.maxconn;
|
||||
} else if (unlimited) {
|
||||
pipes = -1;
|
||||
}
|
||||
|
||||
return pipes >= 4 ? pipes / 4 : pipes;
|
||||
}
|
||||
|
||||
/* considers global.maxsocks, global.maxpipes, async engines, SSL frontends and
|
||||
* rlimits and computes an ideal maxconn. It's meant to be called only when
|
||||
* maxsock contains the sum of listening FDs, before it is updated based on
|
||||
* maxconn and pipes. If there are not enough FDs left, DEFAULT_MAXCONN (by
|
||||
* default 100) is returned as it is expected that it will even run on tight
|
||||
* environments, and will maintain compatibility with previous packages that
|
||||
* used to rely on this value as the default one. The system will emit a
|
||||
* warning indicating how many FDs are missing anyway if needed.
|
||||
*/
|
||||
int compute_ideal_maxconn()
|
||||
{
|
||||
int ssl_sides = !!global.ssl_used_frontend + !!global.ssl_used_backend;
|
||||
int engine_fds = global.ssl_used_async_engines * ssl_sides;
|
||||
int pipes = compute_ideal_maxpipes();
|
||||
int remain = MAX(rlim_fd_cur_at_boot, rlim_fd_max_at_boot);
|
||||
int maxconn;
|
||||
|
||||
/* we have to take into account these elements :
|
||||
* - number of engine_fds, which inflates the number of FD needed per
|
||||
* connection by this number.
|
||||
* - number of pipes per connection on average : for the unlimited
|
||||
* case, this is 0.5 pipe FDs per connection, otherwise it's a
|
||||
* fixed value of 2*pipes.
|
||||
* - two FDs per connection
|
||||
*/
|
||||
|
||||
/* on some modern distros for archs like amd64 fs.nr_open (kernel max)
|
||||
* could be in order of 1 billion. Systemd since the version 256~rc3-3
|
||||
* bumped fs.nr_open as the hard RLIMIT_NOFILE (rlim_fd_max_at_boot).
|
||||
* If we are started without any limits, we risk to finish with computed
|
||||
* maxconn = ~500000000, maxsock = ~2*maxconn. So, fdtab will be
|
||||
* extremely large and watchdog will kill the process, when it will try
|
||||
* to loop over the fdtab (see fd_reregister_all). Please note, that
|
||||
* fd_hard_limit is taken in account implicitly via 'ideal_maxconn'
|
||||
* value in all global.maxconn adjustements, when global.rlimit_memmax
|
||||
* is set:
|
||||
*
|
||||
* MIN(global.maxconn, capped by global.rlimit_memmax, ideal_maxconn);
|
||||
*
|
||||
* It also caps global.rlimit_nofile, if it couldn't be set as rlim_cur
|
||||
* and as rlim_max. So, fd_hard_limitit is a good parameter to serve as
|
||||
* a safeguard, when no haproxy-specific limits are set, i.e.
|
||||
* rlimit_memmax, maxconn, rlimit_nofile. But it must be kept as a zero,
|
||||
* if only one of these ha-specific limits is presented in config or in
|
||||
* the cmdline.
|
||||
*/
|
||||
if (!global.fd_hard_limit && !global.maxconn && !global.rlimit_nofile
|
||||
&& !global.rlimit_memmax)
|
||||
global.fd_hard_limit = DEFAULT_MAXFD;
|
||||
|
||||
if (remain > global.fd_hard_limit)
|
||||
remain = global.fd_hard_limit;
|
||||
|
||||
/* subtract listeners and checks */
|
||||
remain -= global.maxsock;
|
||||
|
||||
/* one epoll_fd/kqueue_fd per thread */
|
||||
remain -= global.nbthread;
|
||||
|
||||
/* one wake-up pipe (2 fd) per thread */
|
||||
remain -= 2 * global.nbthread;
|
||||
|
||||
/* Fixed pipes values : we only subtract them if they're not larger
|
||||
* than the remaining FDs because pipes are optional.
|
||||
*/
|
||||
if (pipes >= 0 && pipes * 2 < remain)
|
||||
remain -= pipes * 2;
|
||||
|
||||
if (pipes < 0) {
|
||||
/* maxsock = maxconn * 2 + maxconn/4 * 2 + maxconn * engine_fds.
|
||||
* = maxconn * (2 + 0.5 + engine_fds)
|
||||
* = maxconn * (4 + 1 + 2*engine_fds) / 2
|
||||
*/
|
||||
maxconn = 2 * remain / (5 + 2 * engine_fds);
|
||||
} else {
|
||||
/* maxsock = maxconn * 2 + maxconn * engine_fds.
|
||||
* = maxconn * (2 + engine_fds)
|
||||
*/
|
||||
maxconn = remain / (2 + engine_fds);
|
||||
}
|
||||
|
||||
return MAX(maxconn, DEFAULT_MAXCONN);
|
||||
}
|
||||
|
||||
/* computes the estimated maxsock value for the given maxconn based on the
|
||||
* possibly set global.maxpipes and existing partial global.maxsock. It may
|
||||
* temporarily change global.maxconn for the time needed to propagate the
|
||||
* computations, and will reset it.
|
||||
*/
|
||||
int compute_ideal_maxsock(int maxconn)
|
||||
{
|
||||
int maxpipes = global.maxpipes;
|
||||
int maxsock = global.maxsock;
|
||||
|
||||
|
||||
if (!maxpipes) {
|
||||
int old_maxconn = global.maxconn;
|
||||
|
||||
global.maxconn = maxconn;
|
||||
maxpipes = compute_ideal_maxpipes();
|
||||
global.maxconn = old_maxconn;
|
||||
}
|
||||
|
||||
maxsock += maxconn * 2; /* each connection needs two sockets */
|
||||
maxsock += maxpipes * 2; /* each pipe needs two FDs */
|
||||
maxsock += global.nbthread; /* one epoll_fd/kqueue_fd per thread */
|
||||
maxsock += 2 * global.nbthread; /* one wake-up pipe (2 fd) per thread */
|
||||
|
||||
/* compute fd used by async engines */
|
||||
if (global.ssl_used_async_engines) {
|
||||
int sides = !!global.ssl_used_frontend + !!global.ssl_used_backend;
|
||||
|
||||
maxsock += maxconn * sides * global.ssl_used_async_engines;
|
||||
}
|
||||
return maxsock;
|
||||
}
|
||||
|
||||
/* Tests if it is possible to set the current process's RLIMIT_NOFILE to
|
||||
* <maxsock>, then sets it back to the previous value. Returns non-zero if the
|
||||
* value is accepted, non-zero otherwise. This is used to determine if an
|
||||
* automatic limit may be applied or not. When it is not, the caller knows that
|
||||
* the highest we can do is the rlim_max at boot. In case of error, we return
|
||||
* that the setting is possible, so that we defer the error processing to the
|
||||
* final stage in charge of enforcing this.
|
||||
*/
|
||||
int check_if_maxsock_permitted(int maxsock)
|
||||
{
|
||||
struct rlimit orig_limit, test_limit;
|
||||
int ret;
|
||||
|
||||
if (global.fd_hard_limit && maxsock > global.fd_hard_limit)
|
||||
return 0;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &orig_limit) != 0)
|
||||
return 1;
|
||||
|
||||
/* don't go further if we can't even set to what we have */
|
||||
if (raise_rlim_nofile(NULL, &orig_limit) != 0)
|
||||
return 1;
|
||||
|
||||
test_limit.rlim_max = MAX(maxsock, orig_limit.rlim_max);
|
||||
test_limit.rlim_cur = test_limit.rlim_max;
|
||||
ret = raise_rlim_nofile(NULL, &test_limit);
|
||||
|
||||
if (raise_rlim_nofile(NULL, &orig_limit) != 0)
|
||||
return 1;
|
||||
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user