From d3aeddb8d19b9bb992ffea6f7a514d90ba697bb9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 15:47:50 +0200 Subject: [PATCH 01/14] update TODO --- TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO b/TODO index e79aeda90e0..957dde27c3a 100644 --- a/TODO +++ b/TODO @@ -48,6 +48,8 @@ Features: * optionally: turn on cgroup delegation for per-session scope units +* introduce per-unit (i.e. per-slice, per-service) journal log size limits. + * optionally, if a per-partition GPT flag is set for the root/home/… partitions format the partition on next boot and unset the flag, in order to implement factory reset. also, add a second flag that simply indicates whether such a From d6e069f412edc02a0f2130fa47955d02e378d5f5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 17:37:01 +0200 Subject: [PATCH 02/14] def: add a "high" limit for RLIMIT_NOFILE This simply adds a new constant we can use for bumping RLIMIT_NOFILE to a "high" value. It default to 256K for now, which is pretty high, but smaller than the kernel built-in limit of 1M. Previously, some tools that needed a higher RLIMIT_NOFILE bumped it to 16K. This new define goes substantially higher than this, following the discussion with the kernel folks. --- src/basic/def.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/def.h b/src/basic/def.h index 4d515c11b6a..05e352bc4f4 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -75,3 +75,5 @@ _CONF_PATHS_SPLIT_USR(n)) #define LONG_LINE_MAX (1U*1024U*1024U) + +#define HIGH_RLIMIT_NOFILE (256*1024) From 99ab6fdf8c68bbe05a127b59ae03d2156d6ea856 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 17:38:48 +0200 Subject: [PATCH 03/14] core: add a new call for bumping RLIMIT_NOFILE to "high" values Following discussions with some kernel folks at All Systems Go! it appears that file descriptors are not really as expensive as they used to be (both memory and performance-wise) and it should thus be OK to allow programs (including unprivileged ones) to have more of them without ill effects. Unfortunately we can't just raise the RLIMIT_NOFILE soft limit globally for all processes, as select() and friends can't handle fds >= 1024, and thus unexpecting programs might fail if they accidently get an fd outside of that range. We can however raise the hard limit, so that programs that need a lot of fds can opt-in into getting fds beyond the 1024 boundary, simply by bumping the soft limit to the now higher hard limit. This is useful for all our client code that accesses the journal, as the journal merging logic might need a lot of fds. Let's add a unified function for bumping the limit in a robust way. --- src/basic/rlimit-util.c | 22 ++++++++++++++++++++++ src/basic/rlimit-util.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index be1ba615ec5..d63b85850cf 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "extract-word.h" +#include "fd-util.h" #include "format-util.h" #include "macro.h" #include "missing.h" @@ -360,3 +361,24 @@ void rlimit_free_all(struct rlimit **rl) { for (i = 0; i < _RLIMIT_MAX; i++) rl[i] = mfree(rl[i]); } + +int rlimit_nofile_bump(int limit) { + int r; + + /* Bumps the (soft) RLIMIT_NOFILE resource limit as close as possible to the specified limit. If a negative + * limit is specified, bumps it to the maximum the kernel and the hard resource limit allows. This call should + * be used by all our programs that might need a lot of fds, and that know how to deal with high fd numbers + * (i.e. do not use select() — which chokes on fds >= 1024) */ + + if (limit < 0) + limit = read_nr_open(); + + if (limit < 3) + limit = 3; + + r = setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(limit)); + if (r < 0) + return log_debug_errno(r, "Failed to set RLIMIT_NOFILE: %m"); + + return 0; +} diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h index c2ea6f846ba..6139af3ff50 100644 --- a/src/basic/rlimit-util.h +++ b/src/basic/rlimit-util.h @@ -20,3 +20,5 @@ int rlimit_format(const struct rlimit *rl, char **ret); void rlimit_free_all(struct rlimit **rl); #define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) + +int rlimit_nofile_bump(int limit); From 1abaf4887dde91bd19b36a80475a8eb03f363590 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 17:44:46 +0200 Subject: [PATCH 04/14] tree-wide: uniformly bump RLIMIT_NOFILE in all our tools that access the journal This makes use of rlimit_nofile_bump() in all tools that access the journal. In some cases this replaces older code to achieve this, and others we add it in where it was missing. --- src/coredump/coredumpctl.c | 5 +++++ src/journal-remote/journal-remote-main.c | 4 ++++ src/journal-remote/journal-upload.c | 4 ++++ src/journal/journalctl.c | 10 +++++----- src/login/loginctl.c | 5 +++++ src/machine/machinectl.c | 6 ++++++ src/systemctl/systemctl.c | 10 ++++------ 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 78e279db8bc..8c08c64884c 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -15,6 +15,7 @@ #include "bus-error.h" #include "bus-util.h" #include "compress.h" +#include "def.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -26,6 +27,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "rlimit-util.h" #include "sigbus.h" #include "signal-util.h" #include "string-util.h" @@ -1067,6 +1069,9 @@ int main(int argc, char *argv[]) { log_parse_environment(); log_open(); + /* The journal merging logic potentially needs a lot of fds. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + r = parse_argv(argc, argv); if (r <= 0) goto end; diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 6d9b44e5152..b52e9329ef2 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -12,6 +12,7 @@ #include "journal-remote-write.h" #include "journal-remote.h" #include "process-util.h" +#include "rlimit-util.h" #include "signal-util.h" #include "socket-util.h" #include "stat-util.h" @@ -1096,6 +1097,9 @@ int main(int argc, char **argv) { log_show_color(true); log_parse_environment(); + /* The journal merging logic potentially needs a lot of fds. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + r = parse_config(); if (r < 0) return EXIT_FAILURE; diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index 621fd620ee2..88fc51ec267 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -20,6 +20,7 @@ #include "mkdir.h" #include "parse-util.h" #include "process-util.h" +#include "rlimit-util.h" #include "sigbus.h" #include "signal-util.h" #include "string-util.h" @@ -780,6 +781,9 @@ int main(int argc, char **argv) { log_show_color(true); log_parse_environment(); + /* The journal merging logic potentially needs a lot of fds. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + r = parse_config(); if (r < 0) goto finish; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 4d186014edb..9bd2d9a1502 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -31,6 +31,7 @@ #include "bus-util.h" #include "catalog.h" #include "chattr-util.h" +#include "def.h" #include "device-private.h" #include "fd-util.h" #include "fileio.h" @@ -2049,6 +2050,10 @@ int main(int argc, char *argv[]) { log_parse_environment(); log_open(); + /* Increase max number of open files if we can, we might needs this when browsing journal files, which might be + * split up into many files. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + r = parse_argv(argc, argv); if (r <= 0) goto finish; @@ -2056,11 +2061,6 @@ int main(int argc, char *argv[]) { signal(SIGWINCH, columns_lines_cache_reset); sigbus_install(); - /* Increase max number of open files to 16K if we can, we - * might needs this when browsing journal files, which might - * be split up into many files. */ - setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); - switch (arg_action) { case ACTION_NEW_ID128: diff --git a/src/login/loginctl.c b/src/login/loginctl.c index c9c3166f0c8..39c24f8c3ae 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -21,6 +21,7 @@ #include "pager.h" #include "parse-util.h" #include "process-util.h" +#include "rlimit-util.h" #include "sigbus.h" #include "signal-util.h" #include "spawn-polkit-agent.h" @@ -1522,6 +1523,10 @@ int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_parse_environment(); log_open(); + + /* The journal merging logic potentially needs a lot of fds. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + sigbus_install(); r = parse_argv(argc, argv); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 2f21f99957d..d408d80c14f 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -22,6 +22,7 @@ #include "cgroup-show.h" #include "cgroup-util.h" #include "copy.h" +#include "def.h" #include "env-util.h" #include "fd-util.h" #include "format-table.h" @@ -37,6 +38,7 @@ #include "path-util.h" #include "process-util.h" #include "ptyfwd.h" +#include "rlimit-util.h" #include "sigbus.h" #include "signal-util.h" #include "spawn-polkit-agent.h" @@ -3030,6 +3032,10 @@ int main(int argc, char*argv[]) { setlocale(LC_ALL, ""); log_parse_environment(); log_open(); + + /* The journal merging logic potentially needs a lot of fds. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + sigbus_install(); r = parse_argv(argc, argv); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 90adeb93a81..5e3040f221d 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -5264,12 +5264,6 @@ static int show(int argc, char *argv[], void *userdata) { (void) pager_open(arg_no_pager, false); - if (show_mode == SYSTEMCTL_SHOW_STATUS) - /* Increase max number of open files to 16K if we can, we - * might needs this when browsing journal files, which might - * be split up into many files. */ - setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); - /* If no argument is specified inspect the manager itself */ if (show_mode == SYSTEMCTL_SHOW_PROPERTIES && argc <= 1) return show_one(bus, "/org/freedesktop/systemd1", NULL, show_mode, &new_line, &ellipsized); @@ -8661,6 +8655,10 @@ int main(int argc, char*argv[]) { setlocale(LC_ALL, ""); log_parse_environment(); log_open(); + + /* The journal merging logic potentially needs a lot of fds. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + sigbus_install(); /* Explicitly not on_tty() to avoid setting cached value. From 52d620757817bc0fa7de3ddbe43024544ced7ea0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 17:56:52 +0200 Subject: [PATCH 05/14] core: raise the RLIMIT_NOFILE hard limit for all services by default Following the discussions with the kernel folks, let's substantially increase the hard limit (but not the soft limit) of RLIMIT_NOFILE to 256K for all services we start. Note that PID 1 itself bumps the limit even further, to the max the kernel allows. We can deal with that after all. --- src/core/main.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index 01f88827fe6..de77d00e870 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1167,13 +1167,15 @@ static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { assert(saved_rlimit); - /* Save the original RLIMIT_NOFILE so that we can reset it - * later when transitioning from the initrd to the main + /* Save the original RLIMIT_NOFILE so that we can reset it later when transitioning from the initrd to the main * systemd or suchlike. */ if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) return log_warning_errno(errno, "Reading RLIMIT_NOFILE failed, ignoring: %m"); - /* Make sure forked processes get the default kernel setting */ + /* Get the underlying absolute limit the kernel enforces */ + nr = read_nr_open(); + + /* Make sure forked processes get limits based on the original kernel setting */ if (!arg_default_rlimit[RLIMIT_NOFILE]) { struct rlimit *rl; @@ -1181,11 +1183,25 @@ static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { if (!rl) return log_oom(); + /* Bump the hard limit for system services to a substantially higher value. The default hard limit + * current kernels set is pretty low (4K), mostly for historical reasons. According to kernel + * developers, the fd handling in recent kernels has been optimized substantially enough, so that we + * can bump the limit now, without paying too high a price in memory or performance. Note however that + * we only bump the hard limit, not the soft limit. That's because select() works the way it works, and + * chokes on fds >= 1024. If we'd bump the soft limit globally, it might accidentally happen to + * unexpecting programs that they get fds higher than what they can process using select(). By only + * bumping the hard limit but leaving the low limit as it is we avoid this pitfall: programs that are + * written by folks aware of the select() problem in mind (and thus use poll()/epoll instead of + * select(), the way everybody should) can explicitly opt into high fds by bumping their soft limit + * beyond 1024, to the hard limit we pass. */ + if (arg_system) + rl->rlim_max = MIN((rlim_t) nr, MAX(rl->rlim_max, (rlim_t) HIGH_RLIMIT_NOFILE)); + arg_default_rlimit[RLIMIT_NOFILE] = rl; } - /* Bump up the resource limit for ourselves substantially, all the way to the maximum the kernel allows */ - nr = read_nr_open(); + /* Bump up the resource limit for ourselves substantially, all the way to the maximum the kernel allows, for + * both hard and soft. */ r = setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(nr)); if (r < 0) return log_warning_errno(r, "Setting RLIMIT_NOFILE failed, ignoring: %m"); From c35ee02c61d17b0656e16b0b2fa0449bda9df507 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 18:08:27 +0200 Subject: [PATCH 06/14] units: bump the RLIMIT_NOFILE soft limit for all services that access the journal This updates the unit files of all our serviecs that deal with journal stuff to use a higher RLIMIT_NOFILE soft limit by default. The new value is the same as used for the new HIGH_RLIMIT_NOFILE we just added. With this we ensure all code that access the journal has higher RLIMIT_NOFILE. The code that runs as daemon via the unit files, the code that is run from the user's command line via C code internal to the relevant tools. In some cases this means we'll redundantly bump the limits as there are tools run both from the command line and as service. --- units/systemd-journal-gatewayd.service.in | 6 +++--- units/systemd-journal-remote.service.in | 4 ++++ units/systemd-journal-upload.service.in | 6 +++--- units/systemd-journald.service.in | 8 +++----- units/systemd-logind.service.in | 6 +++--- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/units/systemd-journal-gatewayd.service.in b/units/systemd-journal-gatewayd.service.in index 9768928c579..7530d4c2dab 100644 --- a/units/systemd-journal-gatewayd.service.in +++ b/units/systemd-journal-gatewayd.service.in @@ -30,9 +30,9 @@ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 SystemCallArchitectures=native LockPersonality=yes -# If there are many split upjournal files we need a lot of fds to -# access them all and combine -LimitNOFILE=16384 +# If there are many split up journal files we need a lot of fds to access them +# all in parallel. +LimitNOFILE=262144 [Install] Also=systemd-journal-gatewayd.socket diff --git a/units/systemd-journal-remote.service.in b/units/systemd-journal-remote.service.in index a94265f215b..c1de676b5ca 100644 --- a/units/systemd-journal-remote.service.in +++ b/units/systemd-journal-remote.service.in @@ -32,5 +32,9 @@ SystemCallArchitectures=native LockPersonality=yes LogsDirectory=journal/remote +# If there are many split up journal files we need a lot of fds to access them +# all in parallel. +LimitNOFILE=262144 + [Install] Also=systemd-journal-remote.socket diff --git a/units/systemd-journal-upload.service.in b/units/systemd-journal-upload.service.in index 42da70f473c..b6920094fb0 100644 --- a/units/systemd-journal-upload.service.in +++ b/units/systemd-journal-upload.service.in @@ -32,9 +32,9 @@ SystemCallArchitectures=native LockPersonality=yes StateDirectory=systemd/journal-upload -# If there are many split up journal files we need a lot of fds to -# access them all and combine -LimitNOFILE=16384 +# If there are many split up journal files we need a lot of fds to access them +# all in parallel. +LimitNOFILE=262144 [Install] WantedBy=multi-user.target diff --git a/units/systemd-journald.service.in b/units/systemd-journald.service.in index 52939e6820b..2c09a8b3527 100644 --- a/units/systemd-journald.service.in +++ b/units/systemd-journald.service.in @@ -35,8 +35,6 @@ SystemCallArchitectures=native LockPersonality=yes IPAddressDeny=any -# Increase the default a bit in order to allow many simultaneous -# services being run since we keep one fd open per service. Also, when -# flushing journal files to disk, we might need a lot of fds when many -# journal files are combined. -LimitNOFILE=16384 +# If there are many split up journal files we need a lot of fds to access them +# all in parallel. +LimitNOFILE=262144 diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in index 5e090bcf238..8b264ff29df 100644 --- a/units/systemd-logind.service.in +++ b/units/systemd-logind.service.in @@ -37,6 +37,6 @@ LockPersonality=yes IPAddressDeny=any FileDescriptorStoreMax=512 -# Increase the default a bit in order to allow many simultaneous -# logins since we keep one fd open per session. -LimitNOFILE=16384 +# Increase the default a bit in order to allow many simultaneous logins since +# we keep one fd open per session. +LimitNOFILE=262144 From a17c17122c304ff3f67f1cbf119fa7116315a7df Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Oct 2018 18:11:52 +0200 Subject: [PATCH 07/14] core: bump RLIMIT_NOFILE soft+hard limit for systemd itself in all cases Previously we'd do this for PID 1 only. Let's do this when running in user mode too, because we know we can handle it. --- src/core/main.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index de77d00e870..ace0bbb15dc 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1213,11 +1213,10 @@ static int bump_rlimit_memlock(struct rlimit *saved_rlimit) { int r; assert(saved_rlimit); - assert(getuid() == 0); - /* BPF_MAP_TYPE_LPM_TRIE bpf maps are charged against RLIMIT_MEMLOCK, even though we have CAP_IPC_LOCK which - * should normally disable such checks. We need them to implement IPAccessAllow= and IPAccessDeny=, hence let's - * bump the value high enough for the root user. */ + /* BPF_MAP_TYPE_LPM_TRIE bpf maps are charged against RLIMIT_MEMLOCK, even if we have CAP_IPC_LOCK which should + * normally disable such checks. We need them to implement IPAccessAllow= and IPAccessDeny=, hence let's bump + * the value high enough for our user. */ if (getrlimit(RLIMIT_MEMLOCK, saved_rlimit) < 0) return log_warning_errno(errno, "Reading RLIMIT_MEMLOCK failed, ignoring: %m"); @@ -1936,11 +1935,9 @@ static int initialize_runtime( if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) log_warning_errno(errno, "Failed to make us a subreaper: %m"); - if (arg_system) { - /* Bump up RLIMIT_NOFILE for systemd itself */ - (void) bump_rlimit_nofile(saved_rlimit_nofile); - (void) bump_rlimit_memlock(saved_rlimit_memlock); - } + /* Bump up RLIMIT_NOFILE for systemd itself */ + (void) bump_rlimit_nofile(saved_rlimit_nofile); + (void) bump_rlimit_memlock(saved_rlimit_memlock); return 0; } From 0bbee2c226788ff12d8fa41b5392ad2ceae4d0e0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Oct 2018 08:41:03 +0200 Subject: [PATCH 08/14] rlimit-util: don't call setrlimit() needlessly if it wouldn't change anything Just a tiny tweak to avoid generating an error if there's no need to. --- src/basic/rlimit-util.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index d63b85850cf..c133f84b7e9 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -34,8 +34,15 @@ int setrlimit_closest(int resource, const struct rlimit *rlim) { if (highest.rlim_max == RLIM_INFINITY) return -EPERM; - fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); - fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); + fixed = (struct rlimit) { + .rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max), + .rlim_max = MIN(rlim->rlim_max, highest.rlim_max), + }; + + /* Shortcut things if we wouldn't change anything. */ + if (fixed.rlim_cur == highest.rlim_cur && + fixed.rlim_max == highest.rlim_max) + return 0; if (setrlimit(resource, &fixed) < 0) return -errno; From 0972c1aefa753a7f54c2f7fcb464c2bc05c30219 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Oct 2018 09:21:45 +0200 Subject: [PATCH 09/14] NEWS: explain the RLIMIT_NOFILE bump --- NEWS | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/NEWS b/NEWS index a8a30aaa07f..c8e6152c2e7 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,31 @@ CHANGES WITH 240 in spe: non-transient services (i.e. those defined with unit files on disk) we will continue to default to Type=simple. + * The Linux kernel's current default RLIMIT_NOFILE resource limit for + userspace processes is set to 1024 (soft) and 4096 + (hard). Previously, systemd passed this on unmodified to all + processes it forked off. With this systemd release the hard limit + systemd passes on is increased to 256K, overriding the kernel's + defaults and substantially increasing the number of simultaneous file + descriptors unprivileged userspace processes can allocate. Note that + the soft limit remains at 1024 for compatibility reasons: the + traditional UNIX select() call cannot deal with file descriptors >= + 1024 and increasing the soft limit globally might thus result in + programs unexpectedly allocating a high file descriptor and thus + failing abnormally when attempting to use it with select() (of + course, programs shouldn't use select() anymore, and prefer + poll()/epoll, but the call unfortunately remains undeservedly popular + at this time). This change reflects the fact that file descriptor + handling in the Linux kernel has been optimized in more recent + kernels and allocating large numbers of them should be much cheaper + both in memory and in performance than it used to be. Programs that + want to take benefit of the increased limit have to "opt-in" into + high file descriptors explicitly by setting their soft limit to the + hard limit during initialization. Of course, when doing that they + must do this acknowledging the fact that they cannot use select() + anymore (and neither can any shared library they use — or any shared + library used by any shared library they use and so on). + CHANGES WITH 239: * NETWORK INTERFACE DEVICE NAMING CHANGES: systemd-udevd's "net_id" From 52d363e32e935c8cbaab8e61ef776ff30f4e5cb9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Oct 2018 18:22:38 +0200 Subject: [PATCH 10/14] mkosi: make kmsg work in our mkosi builds at least --- .mkosi/mkosi.fedora | 1 + 1 file changed, 1 insertion(+) diff --git a/.mkosi/mkosi.fedora b/.mkosi/mkosi.fedora index 63027d9fc79..32e9cefebf2 100644 --- a/.mkosi/mkosi.fedora +++ b/.mkosi/mkosi.fedora @@ -10,6 +10,7 @@ Release=27 [Output] Format=raw_btrfs Bootable=yes +KernelCommandLine=printk.devkmsg=on [Partitions] RootSize=3G From a8b627aaed409a15260c25988970c795bf963812 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Oct 2018 18:23:26 +0200 Subject: [PATCH 11/14] main: bump fs.nr_open + fs.max-file to their largest possible values After discussions with kernel folks, a system with memcg really shouldn't need extra hard limits on file descriptors anymore, as they are properly accounted for by memcg anyway. Hence, let's bump these values to their maximums. This also adds a build time option to turn thiss off, to cover those users who do not want to use memcg. --- NEWS | 11 +++++++ meson.build | 3 ++ meson_options.txt | 4 +++ src/core/main.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/NEWS b/NEWS index c8e6152c2e7..d378b08b701 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,17 @@ CHANGES WITH 240 in spe: anymore (and neither can any shared library they use — or any shared library used by any shared library they use and so on). + * The fs.nr_open and fs.file-max sysctls are now automatically bumped + to the highest possible values, as separate accounting of file + descriptors is no longer necessary, as memcg tracks them correctly as + part of the memory accounting anyway. Thus, from the four limits on + file descriptors currently enforced (fs.file-max, fs.nr_open, + RLIMIT_NOFILE hard, RLIMIT_NOFILE soft) we turn off the first two, + and keep only the latter two. A set of build-time options + (-Dbump-proc-sys-fs-file-max=no and -Dbump-proc-sys-fs-nr-open=no) + has been added to revert this change in behaviour, which might be + an option for systems that turn off memcg in the kernel. + CHANGES WITH 239: * NETWORK INTERFACE DEVICE NAMING CHANGES: systemd-udevd's "net_id" diff --git a/meson.build b/meson.build index 30834c86e38..ee8ab1ae295 100644 --- a/meson.build +++ b/meson.build @@ -73,6 +73,9 @@ sysvrcnd_path = get_option('sysvrcnd-path') conf.set10('HAVE_SYSV_COMPAT', sysvinit_path != '' and sysvrcnd_path != '', description : 'SysV init scripts and rcN.d links are supported') +conf.set10('BUMP_PROC_SYS_FS_FILE_MAX', get_option('bump-proc-sys-fs-file-max')) +conf.set10('BUMP_PROC_SYS_FS_NR_OPEN', get_option('bump-proc-sys-fs-nr-open')) + # join_paths ignore the preceding arguments if an absolute component is # encountered, so this should canonicalize various paths when they are # absolute or relative. diff --git a/meson_options.txt b/meson_options.txt index 83ade5bea41..b5a20fb0e21 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -49,6 +49,10 @@ option('debug-extra', type : 'array', choices : ['hashmap', 'mmap-cache'], value description : 'enable extra debugging') option('memory-accounting-default', type : 'boolean', description : 'enable MemoryAccounting= by default') +option('bump-proc-sys-fs-file-max', type : 'boolean', + description : 'bump /proc/sys/fs/file-max to ULONG_MAX') +option('bump-proc-sys-fs-nr-open', type : 'boolean', + description : 'bump /proc/sys/fs/nr_open to INT_MAX') option('valgrind', type : 'boolean', value : false, description : 'do extra operations to avoid valgrind warnings') option('log-trace', type : 'boolean', value : false, diff --git a/src/core/main.c b/src/core/main.c index ace0bbb15dc..6b910fc91ad 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -73,6 +73,7 @@ #include "stdio-util.h" #include "strv.h" #include "switch-root.h" +#include "sysctl-util.h" #include "terminal-util.h" #include "umask-util.h" #include "user-util.h" @@ -1162,6 +1163,88 @@ static int prepare_reexecute( return 0; } +static void bump_file_max_and_nr_open(void) { + + /* Let's bump fs.file-max and fs.nr_open to their respective maximums. On current kernels large numbers of file + * descriptors are no longer a performance problem and their memory is properly tracked by memcg, thus counting + * them and limiting them in another two layers of limits is unnecessary and just complicates things. This + * function hence turns off 2 of the 4 levels of limits on file descriptors, and makes RLIMIT_NOLIMIT (soft + + * hard) the only ones that really matter. */ + +#if BUMP_PROC_SYS_FS_FILE_MAX || BUMP_PROC_SYS_FS_NR_OPEN + _cleanup_free_ char *t = NULL; + int r; +#endif + +#if BUMP_PROC_SYS_FS_FILE_MAX + /* I so wanted to use STRINGIFY(ULONG_MAX) here, but alas we can't as glibc/gcc define that as + * "(0x7fffffffffffffffL * 2UL + 1UL)". Seriously. 😢 */ + if (asprintf(&t, "%lu\n", ULONG_MAX) < 0) { + log_oom(); + return; + } + + r = sysctl_write("fs/file-max", t); + if (r < 0) + log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, "Failed to bump fs.file-max, ignoring: %m"); +#endif + +#if BUMP_PROC_SYS_FS_FILE_MAX && BUMP_PROC_SYS_FS_NR_OPEN + t = mfree(t); +#endif + +#if BUMP_PROC_SYS_FS_NR_OPEN + int v = INT_MAX; + + /* Arg! The kernel enforces maximum and minimum values on the fs.nr_open, but we don't really know what they + * are. The expression by which the maximum is determined is dependent on the architecture, and is something we + * don't really want to copy to userspace, as it is dependent on implementation details of the kernel. Since + * the kernel doesn't expose the maximum value to us, we can only try and hope. Hence, let's start with + * INT_MAX, and then keep halving the value until we find one that works. Ugly? Yes, absolutely, but kernel + * APIs are kernel APIs, so what do can we do... 🤯 */ + + for (;;) { + int k; + + v &= ~(__SIZEOF_POINTER__ - 1); /* Round down to next multiple of the pointer size */ + if (v < 1024) { + log_warning("Can't bump fs.nr_open, value too small."); + break; + } + + k = read_nr_open(); + if (k < 0) { + log_error_errno(k, "Failed to read fs.nr_open: %m"); + break; + } + if (k >= v) { /* Already larger */ + log_debug("Skipping bump, value is already larger."); + break; + } + + if (asprintf(&t, "%i\n", v) < 0) { + log_oom(); + return; + } + + r = sysctl_write("fs/nr_open", t); + t = mfree(t); + if (r == -EINVAL) { + log_debug("Couldn't write fs.nr_open as %i, halving it.", v); + v /= 2; + continue; + } + if (r < 0) { + log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, "Failed to bump fs.nr_open, ignoring: %m"); + break; + } + + log_debug("Successfully bumped fs.nr_open to %i", v); + break; + } +#endif +} + static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { int r, nr; @@ -1883,6 +1966,7 @@ static int initialize_runtime( machine_id_setup(NULL, arg_machine_id, NULL); loopback_setup(); bump_unix_max_dgram_qlen(); + bump_file_max_and_nr_open(); test_usr(); write_container_id(); } From c8884aceefc85245b9bdfb626e2daf27521259bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Oct 2018 18:31:11 +0200 Subject: [PATCH 12/14] main: introduce a define HIGH_RLIMIT_MEMLOCK similar to HIGH_RLIMIT_NOFILE --- src/basic/def.h | 1 + src/core/main.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/def.h b/src/basic/def.h index 05e352bc4f4..65ad6599992 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -77,3 +77,4 @@ #define LONG_LINE_MAX (1U*1024U*1024U) #define HIGH_RLIMIT_NOFILE (256*1024) +#define HIGH_RLIMIT_MEMLOCK (1024ULL*1024ULL*64ULL) diff --git a/src/core/main.c b/src/core/main.c index 6b910fc91ad..1a95486c03c 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1304,7 +1304,7 @@ static int bump_rlimit_memlock(struct rlimit *saved_rlimit) { if (getrlimit(RLIMIT_MEMLOCK, saved_rlimit) < 0) return log_warning_errno(errno, "Reading RLIMIT_MEMLOCK failed, ignoring: %m"); - r = setrlimit_closest(RLIMIT_MEMLOCK, &RLIMIT_MAKE_CONST(1024ULL*1024ULL*64ULL)); + r = setrlimit_closest(RLIMIT_MEMLOCK, &RLIMIT_MAKE_CONST(HIGH_RLIMIT_MEMLOCK)); if (r < 0) return log_warning_errno(r, "Setting RLIMIT_MEMLOCK failed, ignoring: %m"); From c02b6ee49637e0fe086c7ab25a2a9dc9130f5348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 17 Oct 2018 10:21:48 +0200 Subject: [PATCH 13/14] meson: define @HIGH_RLIMIT_NOFILE@ and use it everywhere --- meson.build | 2 ++ src/basic/def.h | 1 - src/core/system.conf.in | 2 +- units/systemd-journal-gatewayd.service.in | 2 +- units/systemd-journal-remote.service.in | 2 +- units/systemd-journal-upload.service.in | 2 +- units/systemd-journald.service.in | 2 +- units/systemd-logind.service.in | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index ee8ab1ae295..5d5783a591e 100644 --- a/meson.build +++ b/meson.build @@ -75,6 +75,7 @@ conf.set10('HAVE_SYSV_COMPAT', sysvinit_path != '' and sysvrcnd_path != '', conf.set10('BUMP_PROC_SYS_FS_FILE_MAX', get_option('bump-proc-sys-fs-file-max')) conf.set10('BUMP_PROC_SYS_FS_NR_OPEN', get_option('bump-proc-sys-fs-nr-open')) +conf.set('HIGH_RLIMIT_NOFILE', 256*1024) # join_paths ignore the preceding arguments if an absolute component is # encountered, so this should canonicalize various paths when they are @@ -272,6 +273,7 @@ substs.set('SYSTEM_SYSVRCND_PATH', sysvrcnd_path) substs.set('RC_LOCAL_SCRIPT_PATH_START', get_option('rc-local')) substs.set('RC_LOCAL_SCRIPT_PATH_STOP', get_option('halt-local')) substs.set('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default ? 'yes' : 'no') +substs.set('HIGH_RLIMIT_NOFILE', conf.get('HIGH_RLIMIT_NOFILE')) ##################################################################### diff --git a/src/basic/def.h b/src/basic/def.h index 65ad6599992..005cd8d090e 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -76,5 +76,4 @@ #define LONG_LINE_MAX (1U*1024U*1024U) -#define HIGH_RLIMIT_NOFILE (256*1024) #define HIGH_RLIMIT_MEMLOCK (1024ULL*1024ULL*64ULL) diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 639b5818ffb..ef1bbbd948f 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -53,7 +53,7 @@ #DefaultLimitSTACK= #DefaultLimitCORE= #DefaultLimitRSS= -#DefaultLimitNOFILE= +#DefaultLimitNOFILE=1024:@HIGH_RLIMIT_NOFILE@ #DefaultLimitAS= #DefaultLimitNPROC= #DefaultLimitMEMLOCK= diff --git a/units/systemd-journal-gatewayd.service.in b/units/systemd-journal-gatewayd.service.in index 7530d4c2dab..a51d59d1011 100644 --- a/units/systemd-journal-gatewayd.service.in +++ b/units/systemd-journal-gatewayd.service.in @@ -32,7 +32,7 @@ LockPersonality=yes # If there are many split up journal files we need a lot of fds to access them # all in parallel. -LimitNOFILE=262144 +LimitNOFILE=@HIGH_RLIMIT_NOFILE@ [Install] Also=systemd-journal-gatewayd.socket diff --git a/units/systemd-journal-remote.service.in b/units/systemd-journal-remote.service.in index c1de676b5ca..fa8682cd285 100644 --- a/units/systemd-journal-remote.service.in +++ b/units/systemd-journal-remote.service.in @@ -34,7 +34,7 @@ LogsDirectory=journal/remote # If there are many split up journal files we need a lot of fds to access them # all in parallel. -LimitNOFILE=262144 +LimitNOFILE=@HIGH_RLIMIT_NOFILE@ [Install] Also=systemd-journal-remote.socket diff --git a/units/systemd-journal-upload.service.in b/units/systemd-journal-upload.service.in index b6920094fb0..1ded9908779 100644 --- a/units/systemd-journal-upload.service.in +++ b/units/systemd-journal-upload.service.in @@ -34,7 +34,7 @@ StateDirectory=systemd/journal-upload # If there are many split up journal files we need a lot of fds to access them # all in parallel. -LimitNOFILE=262144 +LimitNOFILE=@HIGH_RLIMIT_NOFILE@ [Install] WantedBy=multi-user.target diff --git a/units/systemd-journald.service.in b/units/systemd-journald.service.in index 2c09a8b3527..41cac8cf656 100644 --- a/units/systemd-journald.service.in +++ b/units/systemd-journald.service.in @@ -37,4 +37,4 @@ IPAddressDeny=any # If there are many split up journal files we need a lot of fds to access them # all in parallel. -LimitNOFILE=262144 +LimitNOFILE=@HIGH_RLIMIT_NOFILE@ diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in index 8b264ff29df..961263f6071 100644 --- a/units/systemd-logind.service.in +++ b/units/systemd-logind.service.in @@ -39,4 +39,4 @@ FileDescriptorStoreMax=512 # Increase the default a bit in order to allow many simultaneous logins since # we keep one fd open per session. -LimitNOFILE=262144 +LimitNOFILE=@HIGH_RLIMIT_NOFILE@ From 30538ff10b28f59c7b0b287ab0cba84d7972e607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 17 Oct 2018 14:36:09 +0200 Subject: [PATCH 14/14] meson: simplify definition of MEMORY_ACCOUNTING_DEFAULT Let's just use the simplest form, it doesn't really matter how the define looks after preprocessing. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5d5783a591e..a47d7f9370a 100644 --- a/meson.build +++ b/meson.build @@ -231,7 +231,7 @@ conf.set_quoted('SYSTEMD_EXPORT_PATH', join_paths(rootlib conf.set_quoted('VENDOR_KEYRING_PATH', join_paths(rootlibexecdir, 'import-pubring.gpg')) conf.set_quoted('USER_KEYRING_PATH', join_paths(pkgsysconfdir, 'import-pubring.gpg')) conf.set_quoted('DOCUMENT_ROOT', join_paths(pkgdatadir, 'gatewayd')) -conf.set('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default ? 'true' : 'false') +conf.set10('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default) conf.set_quoted('MEMORY_ACCOUNTING_DEFAULT_YES_NO', memory_accounting_default ? 'yes' : 'no') substs.set('prefix', prefixdir)