quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | LICENSE

commit f3b6d5efc375bedd287897dcaabffec7f9222ea6
parent 959c855734e3af12f35532d76deb1ab85474f8f4
Author: Laslo Hunhold <dev@frign.de>
Date:   Thu, 21 Jan 2021 00:58:01 +0100

Properly set resource limits

Quark sets two rlimits, the maximum number of open file descriptors and
the maximum number of threads. I made a mistake in the calculation of
the former (forgetting about slots) and gave it a bit more headroom so
we don't run into problems. Given the number open file descriptors is
per-process, this can be considered done.

The thread-limit is a different matter, because it's per user. To work
around this, the program tries increasing the per-user-limit by the
number of threads needed. If this hits the upper bound imposed by the
system, we ignore this though, and just carry on. Sadly the getrlimit()
errnos are not as insightful as I'd wish, because it uses EPERM for a
lot of things other than excessive limits, but this is a good
compromise.
If we fail because of CAP_SYS_RESOURCE, which might remain uncaught
previously in case setrlimit on RLIMIT_NOFILE lowers both limits, it
will remain unreported here. However, I wouldn't want to spam the command
line with such an error message when it's only a very high preset limit
by a user triggering a kernel-limit-overflow.
In case it is a lack of CAP_SYS_RESOURCE, this is later reported when
pthread_create() fails, so we cover this case as well and thus give
the user proper feedback on what he can do.

Signed-off-by: Laslo Hunhold <dev@frign.de>

Diffstat:
Mmain.c | 56++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 44 insertions(+), 12 deletions(-)

diff --git a/main.c b/main.c @@ -347,7 +347,14 @@ handle_connections(int *insock, size_t nthreads, size_t nslots, } for (i = 0; i < nthreads; i++) { if (pthread_create(&thread[i], NULL, thread_method, &d[i]) != 0) { - die("pthread_create:"); + if (errno == EAGAIN) { + die("You need to run as root or have " + "CAP_SYS_RESOURCE set, or are trying " + "to create more threads than the " + "system can offer"); + } else { + die("pthread_create:"); + } } } @@ -603,12 +610,20 @@ main(int argc, char *argv[]) handlesignals(sigcleanup); - /* set the fd-limit (3 initial + 4 per thread) */ - rlim.rlim_cur = rlim.rlim_max = 3 + 4 * (2 + nthreads); + /* + * set the maximum number of open file descriptors as needed + * - 3 initial fd's + * - nthreads fd's for the listening socket + * - (nthreads * nslots) fd's for the connection-fd + * - (5 * nthreads) fd's for general purpose thread-use + */ + rlim.rlim_cur = rlim.rlim_max = 3 + nthreads + nthreads * nslots + + 5 * nthreads; if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { if (errno == EPERM) { die("You need to run as root or have " - "CAP_SYS_RESOURCE set"); + "CAP_SYS_RESOURCE set, or are asking for more " + "file descriptors than the system can offer"); } else { die("setrlimit:"); } @@ -645,15 +660,32 @@ main(int argc, char *argv[]) die("signal: Failed to set SIG_IGN on SIGPIPE"); } - /* set the thread limit (2 + nthreads) */ - rlim.rlim_cur = rlim.rlim_max = 2 + nthreads; - if (setrlimit(RLIMIT_NPROC, &rlim) < 0) { - if (errno == EPERM) { - die("You need to run as root or have " - "CAP_SYS_RESOURCE set"); - } else { - die("setrlimit:"); + /* + * try increasing the thread-limit by the number + * of threads we need (which is the only reliable + * workaround I know given the thread-limit is per user + * rather than per process), but ignore EPERM errors, + * because this most probably means the user has already + * set the value to the kernel's limit, and there's not + * much we can do in any other case. + * There's also no danger of overflow as the value + * returned by getrlimit() is way below the limits of the + * rlim_t datatype. + */ + if (getrlimit(RLIMIT_NPROC, &rlim) < 0) { + die("getrlimit:"); + } + if (rlim.rlim_max == RLIM_INFINITY) { + if (rlim.rlim_cur != RLIM_INFINITY) { + /* try increasing current limit by nthreads */ + rlim.rlim_cur += nthreads; } + } else { + /* try increasing current and hard limit by nthreads */ + rlim.rlim_cur = rlim.rlim_max += nthreads; + } + if (setrlimit(RLIMIT_NPROC, &rlim) < 0 && errno != EPERM) { + die("setrlimit()"); } /* limit ourselves to reading the servedir and block further unveils */