4eda52
From 862ded47343a782d70f7d4421a6a2e4e33684e5e Mon Sep 17 00:00:00 2001
4eda52
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
4eda52
Date: Tue, 2 Nov 2021 18:18:21 +0100
4eda52
Subject: [PATCH] procfs-util: fix confusion wrt. quantity limit and maximum
4eda52
 value
4eda52
MIME-Version: 1.0
4eda52
Content-Type: text/plain; charset=UTF-8
4eda52
Content-Transfer-Encoding: 8bit
4eda52
4eda52
From packit/rawhide-arm64 logs:
4eda52
Assertion 'limit >= INT_MAX || get_process_ppid(limit+1, NULL) == -ESRCH' failed at src/test/test-process-util.c:855, function test_get_process_ppid(). Aborting.
4eda52
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
4eda52
4eda52
The kernel has a few different limits. In particular kernel.threads-max can be
4eda52
set to some lower value, and kernel.pid_max can be set to a higher value. This
4eda52
is nice because it reduces PID reuse, even if the number of threads that is
4eda52
allowed is limited. But the tests assumed that we cannot have a thread with
4eda52
PID above MIN(kernel.threads-max, kernel.pid_max-1), which is not valid.
4eda52
4eda52
So let's rework the whole thing: let's expose the helpers to read
4eda52
kernel.threads-max and kernel.pid_max, and print what they return in tests.
4eda52
procfs_tasks_get_limit() was something that is only used in tests, and wasn't
4eda52
very well defined, so let's drop it.
4eda52
4eda52
Fixes #21193.
4eda52
4eda52
(cherry picked from commit c3dead53d50e334f2d072a2248256983d6dc9f8c)
4eda52
4eda52
Related: #2017035
4eda52
---
4eda52
 src/basic/limits-util.c      | 50 ++++++++++++++++++++++++----------
4eda52
 src/basic/procfs-util.c      | 53 +++++++++---------------------------
4eda52
 src/basic/procfs-util.h      |  4 ++-
4eda52
 src/test/test-process-util.c | 10 +++++--
4eda52
 src/test/test-procfs-util.c  | 34 ++++++++++++++++-------
4eda52
 5 files changed, 84 insertions(+), 67 deletions(-)
4eda52
4eda52
diff --git a/src/basic/limits-util.c b/src/basic/limits-util.c
4eda52
index 9f8e26d46a..435a2a0efe 100644
4eda52
--- a/src/basic/limits-util.c
4eda52
+++ b/src/basic/limits-util.c
4eda52
@@ -109,35 +109,57 @@ uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
4eda52
 }
4eda52
 
4eda52
 uint64_t system_tasks_max(void) {
4eda52
-        uint64_t a = TASKS_MAX, b = TASKS_MAX;
4eda52
+        uint64_t a = TASKS_MAX, b = TASKS_MAX, c = TASKS_MAX;
4eda52
         _cleanup_free_ char *root = NULL;
4eda52
         int r;
4eda52
 
4eda52
-        /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
4eda52
-         * limit:
4eda52
+        /* Determine the maximum number of tasks that may run on this system. We check three sources to
4eda52
+         * determine this limit:
4eda52
          *
4eda52
-         * a) the maximum tasks value the kernel allows on this architecture
4eda52
-         * b) the cgroups pids_max attribute for the system
4eda52
-         * c) the kernel's configured maximum PID value
4eda52
+         * a) kernel.threads-max sysctl: the maximum number of tasks (threads) the kernel allows.
4eda52
          *
4eda52
-         * And then pick the smallest of the three */
4eda52
+         *    This puts a direct limit on the number of concurrent tasks.
4eda52
+         *
4eda52
+         * b) kernel.pid_max sysctl: the maximum PID value.
4eda52
+         *
4eda52
+         *    This limits the numeric range PIDs can take, and thus indirectly also limits the number of
4eda52
+         *    concurrent threads. It's primarily a compatibility concept: some crappy old code used a signed
4eda52
+         *    16bit type for PIDs, hence the kernel provides a way to ensure the PIDs never go beyond
4eda52
+         *    INT16_MAX by default.
4eda52
+         *
4eda52
+         *    Also note the weird definition: PIDs assigned will be kept below this value, which means
4eda52
+         *    the number of tasks that can be created is one lower, as PID 0 is not a valid process ID.
4eda52
+         *
4eda52
+         * c) pids.max on the root cgroup: the kernel's configured maximum number of tasks.
4eda52
+         *
4eda52
+         * and then pick the smallest of the three.
4eda52
+         *
4eda52
+         * By default pid_max is set to much lower values than threads-max, hence the limit people come into
4eda52
+         * contact with first, as it's the lowest boundary they need to bump when they want higher number of
4eda52
+         * processes.
4eda52
+         */
4eda52
+
4eda52
+        r = procfs_get_threads_max(&a);
4eda52
+        if (r < 0)
4eda52
+                log_debug_errno(r, "Failed to read kernel.threads-max, ignoring: %m");
4eda52
 
4eda52
-        r = procfs_tasks_get_limit(&a);
4eda52
+        r = procfs_get_pid_max(&b);
4eda52
         if (r < 0)
4eda52
-                log_debug_errno(r, "Failed to read maximum number of tasks from /proc, ignoring: %m");
4eda52
+                log_debug_errno(r, "Failed to read kernel.pid_max, ignoring: %m");
4eda52
+        else if (b > 0)
4eda52
+                /* Subtract one from pid_max, since PID 0 is not a valid PID */
4eda52
+                b--;
4eda52
 
4eda52
         r = cg_get_root_path(&root);
4eda52
         if (r < 0)
4eda52
                 log_debug_errno(r, "Failed to determine cgroup root path, ignoring: %m");
4eda52
         else {
4eda52
-                r = cg_get_attribute_as_uint64("pids", root, "pids.max", &b);
4eda52
+                r = cg_get_attribute_as_uint64("pids", root, "pids.max", &c);
4eda52
                 if (r < 0)
4eda52
-                        log_debug_errno(r, "Failed to read pids.max attribute of cgroup root, ignoring: %m");
4eda52
+                        log_debug_errno(r, "Failed to read pids.max attribute of root cgroup, ignoring: %m");
4eda52
         }
4eda52
 
4eda52
-        return MIN3(TASKS_MAX,
4eda52
-                    a <= 0 ? TASKS_MAX : a,
4eda52
-                    b <= 0 ? TASKS_MAX : b);
4eda52
+        return MIN3(a, b, c);
4eda52
 }
4eda52
 
4eda52
 uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
4eda52
diff --git a/src/basic/procfs-util.c b/src/basic/procfs-util.c
4eda52
index 9234ccaf85..a29e776a3a 100644
4eda52
--- a/src/basic/procfs-util.c
4eda52
+++ b/src/basic/procfs-util.c
4eda52
@@ -12,54 +12,34 @@
4eda52
 #include "stdio-util.h"
4eda52
 #include "string-util.h"
4eda52
 
4eda52
-int procfs_tasks_get_limit(uint64_t *ret) {
4eda52
+int procfs_get_pid_max(uint64_t *ret) {
4eda52
         _cleanup_free_ char *value = NULL;
4eda52
-        uint64_t pid_max, threads_max;
4eda52
         int r;
4eda52
 
4eda52
         assert(ret);
4eda52
 
4eda52
-        /* So there are two sysctl files that control the system limit of processes:
4eda52
-         *
4eda52
-         * 1. kernel.threads-max: this is probably the sysctl that makes more sense, as it directly puts a limit on
4eda52
-         *    concurrent tasks.
4eda52
-         *
4eda52
-         * 2. kernel.pid_max: this limits the numeric range PIDs can take, and thus indirectly also limits the number
4eda52
-         *    of concurrent threads. AFAICS it's primarily a compatibility concept: some crappy old code used a signed
4eda52
-         *    16bit type for PIDs, hence the kernel provides a way to ensure the PIDs never go beyond INT16_MAX by
4eda52
-         *    default.
4eda52
-         *
4eda52
-         * By default #2 is set to much lower values than #1, hence the limit people come into contact with first, as
4eda52
-         * it's the lowest boundary they need to bump when they want higher number of processes.
4eda52
-         *
4eda52
-         * Also note the weird definition of #2: PIDs assigned will be kept below this value, which means the number of
4eda52
-         * tasks that can be created is one lower, as PID 0 is not a valid process ID. */
4eda52
-
4eda52
         r = read_one_line_file("/proc/sys/kernel/pid_max", &value);
4eda52
         if (r < 0)
4eda52
                 return r;
4eda52
 
4eda52
-        r = safe_atou64(value, &pid_max);
4eda52
-        if (r < 0)
4eda52
-                return r;
4eda52
+        return safe_atou64(value, ret);
4eda52
+}
4eda52
 
4eda52
-        value = mfree(value);
4eda52
-        r = read_one_line_file("/proc/sys/kernel/threads-max", &value);
4eda52
-        if (r < 0)
4eda52
-                return r;
4eda52
+int procfs_get_threads_max(uint64_t *ret) {
4eda52
+        _cleanup_free_ char *value = NULL;
4eda52
+        int r;
4eda52
 
4eda52
-        r = safe_atou64(value, &threads_max);
4eda52
+        assert(ret);
4eda52
+
4eda52
+        r = read_one_line_file("/proc/sys/kernel/threads-max", &value);
4eda52
         if (r < 0)
4eda52
                 return r;
4eda52
 
4eda52
-        /* Subtract one from pid_max, since PID 0 is not a valid PID */
4eda52
-        *ret = MIN(pid_max-1, threads_max);
4eda52
-        return 0;
4eda52
+        return safe_atou64(value, ret);
4eda52
 }
4eda52
 
4eda52
 int procfs_tasks_set_limit(uint64_t limit) {
4eda52
         char buffer[DECIMAL_STR_MAX(uint64_t)+1];
4eda52
-        _cleanup_free_ char *value = NULL;
4eda52
         uint64_t pid_max;
4eda52
         int r;
4eda52
 
4eda52
@@ -74,10 +54,7 @@ int procfs_tasks_set_limit(uint64_t limit) {
4eda52
          * set it to the maximum. */
4eda52
         limit = CLAMP(limit, 20U, TASKS_MAX);
4eda52
 
4eda52
-        r = read_one_line_file("/proc/sys/kernel/pid_max", &value);
4eda52
-        if (r < 0)
4eda52
-                return r;
4eda52
-        r = safe_atou64(value, &pid_max);
4eda52
+        r = procfs_get_pid_max(&pid_max);
4eda52
         if (r < 0)
4eda52
                 return r;
4eda52
 
4eda52
@@ -98,14 +75,10 @@ int procfs_tasks_set_limit(uint64_t limit) {
4eda52
                 /* Hmm, we couldn't write this? If so, maybe it was already set properly? In that case let's not
4eda52
                  * generate an error */
4eda52
 
4eda52
-                value = mfree(value);
4eda52
-                if (read_one_line_file("/proc/sys/kernel/threads-max", &value) < 0)
4eda52
-                        return r; /* return original error */
4eda52
-
4eda52
-                if (safe_atou64(value, &threads_max) < 0)
4eda52
+                if (procfs_get_threads_max(&threads_max) < 0)
4eda52
                         return r; /* return original error */
4eda52
 
4eda52
-                if (MIN(pid_max-1, threads_max) != limit)
4eda52
+                if (MIN(pid_max - 1, threads_max) != limit)
4eda52
                         return r; /* return original error */
4eda52
 
4eda52
                 /* Yay! Value set already matches what we were trying to set, hence consider this a success. */
4eda52
diff --git a/src/basic/procfs-util.h b/src/basic/procfs-util.h
4eda52
index 61fa71d479..eb8c7738b1 100644
4eda52
--- a/src/basic/procfs-util.h
4eda52
+++ b/src/basic/procfs-util.h
4eda52
@@ -5,7 +5,9 @@
4eda52
 
4eda52
 #include "time-util.h"
4eda52
 
4eda52
-int procfs_tasks_get_limit(uint64_t *ret);
4eda52
+int procfs_get_pid_max(uint64_t *ret);
4eda52
+int procfs_get_threads_max(uint64_t *ret);
4eda52
+
4eda52
 int procfs_tasks_set_limit(uint64_t limit);
4eda52
 int procfs_tasks_get_current(uint64_t *ret);
4eda52
 
4eda52
diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c
4eda52
index 8c76392ae9..d89ce6e2db 100644
4eda52
--- a/src/test/test-process-util.c
4eda52
+++ b/src/test/test-process-util.c
4eda52
@@ -850,8 +850,14 @@ static void test_get_process_ppid(void) {
4eda52
         assert_se(get_process_ppid(1, NULL) == -EADDRNOTAVAIL);
4eda52
 
4eda52
         /* the process with the PID above the global limit definitely doesn't exist. Verify that */
4eda52
-        assert_se(procfs_tasks_get_limit(&limit) >= 0);
4eda52
-        assert_se(limit >= INT_MAX || get_process_ppid(limit+1, NULL) == -ESRCH);
4eda52
+        assert_se(procfs_get_pid_max(&limit) >= 0);
4eda52
+        log_debug("kernel.pid_max = %"PRIu64, limit);
4eda52
+
4eda52
+        if (limit < INT_MAX) {
4eda52
+                r = get_process_ppid(limit + 1, NULL);
4eda52
+                log_debug_errno(r, "get_process_limit(%"PRIu64") → %d/%m", limit + 1, r);
4eda52
+                assert(r == -ESRCH);
4eda52
+        }
4eda52
 
4eda52
         for (pid_t pid = 0;;) {
4eda52
                 _cleanup_free_ char *c1 = NULL, *c2 = NULL;
4eda52
diff --git a/src/test/test-procfs-util.c b/src/test/test-procfs-util.c
4eda52
index b2679e30fb..876ef40dfd 100644
4eda52
--- a/src/test/test-procfs-util.c
4eda52
+++ b/src/test/test-procfs-util.c
4eda52
@@ -6,12 +6,13 @@
4eda52
 #include "format-util.h"
4eda52
 #include "log.h"
4eda52
 #include "procfs-util.h"
4eda52
+#include "process-util.h"
4eda52
 #include "tests.h"
4eda52
 
4eda52
 int main(int argc, char *argv[]) {
4eda52
         char buf[CONST_MAX(FORMAT_TIMESPAN_MAX, FORMAT_BYTES_MAX)];
4eda52
         nsec_t nsec;
4eda52
-        uint64_t v;
4eda52
+        uint64_t v, w;
4eda52
         int r;
4eda52
 
4eda52
         log_parse_environment();
4eda52
@@ -26,26 +27,39 @@ int main(int argc, char *argv[]) {
4eda52
         assert_se(procfs_tasks_get_current(&v) >= 0);
4eda52
         log_info("Current number of tasks: %" PRIu64, v);
4eda52
 
4eda52
-        r = procfs_tasks_get_limit(&v);
4eda52
-        if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r))
4eda52
-                return log_tests_skipped("can't read /proc/sys/kernel/pid_max");
4eda52
+        v = TASKS_MAX;
4eda52
+        r = procfs_get_pid_max(&v);
4eda52
+        assert(r >= 0 || r == -ENOENT || ERRNO_IS_PRIVILEGE(r));
4eda52
+        log_info("kernel.pid_max: %"PRIu64, v);
4eda52
+
4eda52
+        w = TASKS_MAX;
4eda52
+        r = procfs_get_threads_max(&w);
4eda52
+        assert(r >= 0 || r == -ENOENT || ERRNO_IS_PRIVILEGE(r));
4eda52
+        log_info("kernel.threads-max: %"PRIu64, w);
4eda52
+
4eda52
+        v = MIN(v - (v > 0), w);
4eda52
 
4eda52
         assert_se(r >= 0);
4eda52
         log_info("Limit of tasks: %" PRIu64, v);
4eda52
         assert_se(v > 0);
4eda52
-        assert_se(procfs_tasks_set_limit(v) >= 0);
4eda52
+        r = procfs_tasks_set_limit(v);
4eda52
+        if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r))
4eda52
+                return log_tests_skipped("can't set task limits");
4eda52
+        assert(r >= 0);
4eda52
 
4eda52
         if (v > 100) {
4eda52
-                uint64_t w;
4eda52
+                log_info("Reducing limit by one to %"PRIu64"…", v-1);
4eda52
+
4eda52
                 r = procfs_tasks_set_limit(v-1);
4eda52
-                assert_se(IN_SET(r, 0, -EPERM, -EACCES, -EROFS));
4eda52
+                log_info_errno(r, "procfs_tasks_set_limit: %m");
4eda52
+                assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r));
4eda52
 
4eda52
-                assert_se(procfs_tasks_get_limit(&w) >= 0);
4eda52
-                assert_se((r == 0 && w == v - 1) || (r < 0 && w == v));
4eda52
+                assert_se(procfs_get_threads_max(&w) >= 0);
4eda52
+                assert_se(r >= 0 ? w == v - 1 : w == v);
4eda52
 
4eda52
                 assert_se(procfs_tasks_set_limit(v) >= 0);
4eda52
 
4eda52
-                assert_se(procfs_tasks_get_limit(&w) >= 0);
4eda52
+                assert_se(procfs_get_threads_max(&w) >= 0);
4eda52
                 assert_se(v == w);
4eda52
         }
4eda52