[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.
This commit is contained in:
Jean Boussier 2023-02-16 19:36:31 +01:00 коммит произвёл Jean Boussier
Родитель 70ea58bd5b
Коммит 1db8951d3a
2 изменённых файлов: 41 добавлений и 3 удалений

Просмотреть файл

@ -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;

Просмотреть файл

@ -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]'