]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
process-util: add getpid_cached() as a caching wrapper for getpid()
authorLennart Poettering <lennart@poettering.net>
Thu, 20 Jul 2017 13:46:05 +0000 (15:46 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 20 Jul 2017 18:27:24 +0000 (20:27 +0200)
Let's make getpid() fast again.

src/basic/process-util.c
src/basic/process-util.h
src/test/test-process-util.c

index b80cacaa42ec646dac66b90adf6f80095f993732..98adee6d9f3bac4709cc1d1f3f7a5412fee0ed91 100644 (file)
@@ -922,6 +922,68 @@ int ioprio_parse_priority(const char *s, int *ret) {
         return 0;
 }
 
+/* The cached PID, possible values:
+ *
+ *     == UNSET [0]  → cache not initialized yet
+ *     == BUSY [-1]  → some thread is initializing it at the moment
+ *     any other     → the cached PID
+ */
+
+#define CACHED_PID_UNSET ((pid_t) 0)
+#define CACHED_PID_BUSY ((pid_t) -1)
+
+static pid_t cached_pid = CACHED_PID_UNSET;
+
+static void reset_cached_pid(void) {
+        /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */
+        cached_pid = CACHED_PID_UNSET;
+}
+
+/* We use glibc __register_atfork() + __dso_handle directly here, as they are not included in the glibc
+ * headers. __register_atfork() is mostly equivalent to pthread_atfork(), but doesn't require us to link against
+ * libpthread, as it is part of glibc anyway. */
+extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void * __dso_handle);
+extern void* __dso_handle __attribute__ ((__weak__));
+
+pid_t getpid_cached(void) {
+        pid_t current_value;
+
+        /* getpid_cached() is much like getpid(), but caches the value in local memory, to avoid having to invoke a
+         * system call each time. This restores glibc behaviour from before 2.24, when getpid() was unconditionally
+         * cached. Starting with 2.24 getpid() started to become prohibitively expensive when used for detecting when
+         * objects were used across fork()s. With this caching the old behaviour is somewhat restored.
+         *
+         * https://bugzilla.redhat.com/show_bug.cgi?id=1443976
+         * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=1d2bc2eae969543b89850e35e532f3144122d80a
+         */
+
+        current_value = __sync_val_compare_and_swap(&cached_pid, CACHED_PID_UNSET, CACHED_PID_BUSY);
+
+        switch (current_value) {
+
+        case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */
+                pid_t new_pid;
+
+                new_pid = getpid();
+
+                if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) {
+                        /* OOM? Let's try again later */
+                        cached_pid = CACHED_PID_UNSET;
+                        return new_pid;
+                }
+
+                cached_pid = new_pid;
+                return new_pid;
+        }
+
+        case CACHED_PID_BUSY: /* Somebody else is currently initializing */
+                return getpid();
+
+        default: /* Properly initialized */
+                return current_value;
+        }
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
index 28d8d7499a5060fde2126ab50cbac1937a142dcb..17746b4ebfa583cf6f6b4cc4f50712bf46ac71b2 100644 (file)
@@ -119,3 +119,5 @@ static inline bool ioprio_priority_is_valid(int i) {
 }
 
 int ioprio_parse_priority(const char *s, int *ret);
+
+pid_t getpid_cached(void);
index c5edbcc5d241b911ac1763ea389556ed92e5ba9a..f78a8c0b5321db7b07cc18bc0c288b3369d3fef3 100644 (file)
@@ -410,6 +410,61 @@ static void test_rename_process(void) {
         test_rename_process_one("1234567", 1); /* should always fit */
 }
 
+static void test_getpid_cached(void) {
+        siginfo_t si;
+        pid_t a, b, c, d, e, f, child;
+
+        a = raw_getpid();
+        b = getpid_cached();
+        c = getpid();
+
+        assert_se(a == b && a == c);
+
+        child = fork();
+        assert_se(child >= 0);
+
+        if (child == 0) {
+                /* In child */
+                a = raw_getpid();
+                b = getpid_cached();
+                c = getpid();
+
+                assert_se(a == b && a == c);
+                _exit(0);
+        }
+
+        d = raw_getpid();
+        e = getpid_cached();
+        f = getpid();
+
+        assert_se(a == d && a == e && a == f);
+
+        assert_se(wait_for_terminate(child, &si) >= 0);
+        assert_se(si.si_status == 0);
+        assert_se(si.si_code == CLD_EXITED);
+}
+
+#define MEASURE_ITERATIONS (10000000LLU)
+
+static void test_getpid_measure(void) {
+        unsigned long long i;
+        usec_t t, q;
+
+        t = now(CLOCK_MONOTONIC);
+        for (i = 0; i < MEASURE_ITERATIONS; i++)
+                (void) getpid();
+        q = now(CLOCK_MONOTONIC) - t;
+
+        log_info(" glibc getpid(): %llu/s\n", (unsigned long long) (MEASURE_ITERATIONS*USEC_PER_SEC/q));
+
+        t = now(CLOCK_MONOTONIC);
+        for (i = 0; i < MEASURE_ITERATIONS; i++)
+                (void) getpid_cached();
+        q = now(CLOCK_MONOTONIC) - t;
+
+        log_info("getpid_cached(): %llu/s\n", (unsigned long long) (MEASURE_ITERATIONS*USEC_PER_SEC/q));
+}
+
 int main(int argc, char *argv[]) {
 
         log_set_max_level(LOG_DEBUG);
@@ -434,6 +489,8 @@ int main(int argc, char *argv[]) {
         test_personality();
         test_get_process_cmdline_harder();
         test_rename_process();
+        test_getpid_cached();
+        test_getpid_measure();
 
         return 0;
 }