Properly set resource limits - quark - quark web server | |
git clone git://git.suckless.org/quark | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit f3b6d5efc375bedd287897dcaabffec7f9222ea6 | |
parent 959c855734e3af12f35532d76deb1ab85474f8f4 | |
Author: Laslo Hunhold <[email protected]> | |
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 <[email protected]> | |
Diffstat: | |
M main.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 ns… | |
} | |
for (i = 0; i < nthreads; i++) { | |
if (pthread_create(&thread[i], NULL, thread_method, &d[i]) != … | |
- 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 u… |