Properly set resource limits - quark - quark web server
 (HTM) git clone git://git.suckless.org/quark
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit f3b6d5efc375bedd287897dcaabffec7f9222ea6
 (DIR) parent 959c855734e3af12f35532d76deb1ab85474f8f4
 (HTM) 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:
         M main.c                              |      56 ++++++++++++++++++++++++-------
       
       1 file changed, 44 insertions(+), 12 deletions(-)
       ---
 (DIR) 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 */