diff options
author | Jean Boussier <jean.boussier@gmail.com> | 2024-05-29 16:46:04 +0200 |
---|---|---|
committer | Jean Boussier <jean.boussier@gmail.com> | 2024-09-05 11:43:46 +0200 |
commit | 63cbe3f6ac9feb44a2e43b1f853e2ca7e049316c (patch) | |
tree | dddbbbc8678332d16f9cf5e9630645e233656797 /thread_pthread.c | |
parent | 2e5680d304a9cf9a6a2ba582091af6719e839351 (diff) |
Proof of Concept: Allow to prevent fork from happening in known fork unsafe API
[Feature #20590] For better of for worse, fork(2) remain the primary provider of parallelism in Ruby programs. Even though it's frowned uppon in many circles, and a lot of literature will simply state that only async-signal safe APIs are safe to use after `fork()`, in practice most APIs work well as long as you are careful about not forking while another thread is holding a pthread mutex. One of the APIs that is known cause fork safety issues is `getaddrinfo`. If you fork while another thread is inside `getaddrinfo`, a mutex may be left locked in the child, with no way to unlock it. I think we could reduce the impact of these problem by preventing in for the most notorious and common cases, by locking around `fork(2)` and known unsafe APIs with a read-write lock.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/10864
Diffstat (limited to 'thread_pthread.c')
-rw-r--r-- | thread_pthread.c | 46 |
1 files changed, 46 insertions, 0 deletions
diff --git a/thread_pthread.c b/thread_pthread.c index 3ebb67aaa3..3c7fba42d2 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -3346,6 +3346,52 @@ native_sleep(rb_thread_t *th, rb_hrtime_t *rel) RUBY_DEBUG_LOG("wakeup"); } +// fork read-write lock (only for pthread) +static pthread_rwlock_t rb_thread_fork_rw_lock = PTHREAD_RWLOCK_INITIALIZER; + +void +rb_thread_release_fork_lock(void) +{ + int r; + if ((r = pthread_rwlock_unlock(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_unlock", r); + } +} + +void +rb_thread_reset_fork_lock(void) +{ + int r; + if ((r = pthread_rwlock_destroy(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_destroy", r); + } + + if ((r = pthread_rwlock_init(&rb_thread_fork_rw_lock, NULL))) { + rb_bug_errno("pthread_rwlock_init", r); + } +} + +void * +rb_thread_prevent_fork(void *(*func)(void *), void *data) +{ + int r; + if ((r = pthread_rwlock_rdlock(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_rdlock", r); + } + void *result = func(data); + rb_thread_release_fork_lock(); + return result; +} + +void +rb_thread_acquire_fork_lock(void) +{ + int r; + if ((r = pthread_rwlock_wrlock(&rb_thread_fork_rw_lock))) { + rb_bug_errno("pthread_rwlock_wrlock", r); + } +} + // thread internal event hooks (only for pthread) struct rb_internal_thread_event_hook { |