From 1db8951d3a8be6a756c9d3d3b87231997b301985 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 16 Feb 2023 19:36:31 +0100 Subject: Cache `Process.pid` [Feature #19443] It's not uncommon for database client and similar network libraries to protect themselves from Process.fork by regularly checking Process.pid Until recently most libc would cache `getpid()` so this was a cheap check to make. However as of glibc version 2.25 the PID cache is removed and calls to `getpid()` always invoke the actual system call which significantly degrades the performance of existing applications. The reason glibc removed the cache is that some libraries were bypassing `fork(2)` by issuing system calls themselves, causing stale cache issues. That isn't a concern for Ruby as bypassing MRI's primitive for forking would render the VM unusable, so we can safely cache the PID. --- process.c | 24 +++++++++++++++++++++--- test/ruby/test_process.rb | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/process.c b/process.c index ca09357d17..9f2a7b9337 100644 --- a/process.c +++ b/process.c @@ -359,6 +359,8 @@ static ID id_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC; #endif static ID id_hertz; +static VALUE cached_pid = Qnil; + /* execv and execl are async-signal-safe since SUSv4 (POSIX.1-2008, XPG7) */ #if defined(__sun) && !defined(_XPG7) /* Solaris 10, 9, ... */ #define execv(path, argv) (rb_async_bug_errno("unreachable: async-signal-unsafe execv() is called", 0)) @@ -497,7 +499,23 @@ parent_redirect_close(int fd) static VALUE get_pid(void) { - return PIDT2NUM(getpid()); + if (UNLIKELY(NIL_P(cached_pid))) { + cached_pid = PIDT2NUM(getpid()); + } + return cached_pid; +} + +static void +clear_pid_cache(void) +{ + cached_pid = Qnil; +} + +static inline void +rb_process_atfork(void) +{ + clear_pid_cache(); + rb_thread_atfork(); /* calls mjit_resume() */ } /* @@ -4059,7 +4077,7 @@ rb_fork_ruby2(struct rb_process_status *status) disable_child_handler_fork_parent(&old); /* yes, bad name */ if (pid >= 0) { /* fork succeed */ - if (pid == 0) rb_thread_atfork(); + if (pid == 0) rb_process_atfork(); return pid; } @@ -6832,7 +6850,7 @@ rb_daemon(int nochdir, int noclose) before_fork_ruby(); err = daemon(nochdir, noclose); after_fork_ruby(); - rb_thread_atfork(); + rb_process_atfork(); #else int n; diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index beb181ccd4..3d0b11547e 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -2586,6 +2586,26 @@ EOS end end if Process.respond_to?(:_fork) + def test__fork_pid_cache + parent_pid = Process.pid + r, w = IO.pipe + pid = Process._fork + if pid == 0 + begin + r.close + w << "ok: #{Process.pid}" + w.close + ensure + exit! + end + else + w.close + assert_equal("ok: #{pid}", r.read) + r.close + Process.waitpid(pid) + end + end if Process.respond_to?(:_fork) + def test__fork_hook %w(fork Process.fork).each do |method| feature17795 = '[ruby-core:103400] [Feature #17795]' -- cgit v1.2.1