aboutsummaryrefslogtreecommitdiffstats
path: root/internal
diff options
context:
space:
mode:
authorKJ Tsanaktsidis <kj@kjtsanaktsidis.id.au>2023-05-21 22:29:16 +1000
committerNobuyoshi Nakada <nobu@ruby-lang.org>2023-05-26 14:51:23 +0900
commit66871c5a06d723f8350935ced1e88d8cc929d809 (patch)
tree71cf77cf24923b1c10695a1b07e06742353cbfc8 /internal
parent54a74c42033e42869e69e7dc9e67efa1faf225be (diff)
downloadruby-66871c5a06d723f8350935ced1e88d8cc929d809.tar.gz
Fix busy-loop when waiting for file descriptors to close
When one thread is closing a file descriptor whilst another thread is concurrently reading it, we need to wait for the reading thread to be done with it to prevent a potential EBADF (or, worse, file descriptor reuse). At the moment, that is done by keeping a list of threads still using the file descriptor in io_close_fptr. It then continually calls rb_thread_schedule() in fptr_finalize_flush until said list is empty. That busy-looping seems to behave rather poorly on some OS's, particulary FreeBSD. It can cause the TestIO#test_race_gets_and_close test to fail (even with its very long 200 second timeout) because the closing thread starves out the using thread. To fix that, I introduce the concept of struct rb_io_close_wait_list; a list of threads still using a file descriptor that we want to close. We call `rb_notify_fd_close` to let the thread scheduler know we're closing a FD, which fills the list with threads. Then, we call rb_notify_fd_close_wait which will block the thread until all of the still-using threads are done. This is implemented with a condition variable sleep, so no busy-looping is required.
Diffstat (limited to 'internal')
-rw-r--r--internal/thread.h10
1 files changed, 10 insertions, 0 deletions
diff --git a/internal/thread.h b/internal/thread.h
index ac7e46b9be..7a6a860aeb 100644
--- a/internal/thread.h
+++ b/internal/thread.h
@@ -10,6 +10,8 @@
*/
#include "ruby/ruby.h" /* for VALUE */
#include "ruby/intern.h" /* for rb_blocking_function_t */
+#include "ccan/list/list.h" /* for list in rb_io_close_wait_list */
+#include "ruby/thread_native.h" /* for mutexes in rb_io_close_wait_list */
struct rb_thread_struct; /* in vm_core.h */
@@ -52,6 +54,14 @@ VALUE rb_exec_recursive_outer_mid(VALUE (*f)(VALUE g, VALUE h, int r), VALUE g,
int rb_thread_wait_for_single_fd(int fd, int events, struct timeval * timeout);
+struct rb_io_close_wait_list {
+ struct ccan_list_head list;
+ rb_nativethread_lock_t mu;
+ rb_nativethread_cond_t cv;
+};
+int rb_notify_fd_close(int fd, struct rb_io_close_wait_list *busy);
+void rb_notify_fd_close_wait(struct rb_io_close_wait_list *busy);
+
RUBY_SYMBOL_EXPORT_BEGIN
/* Temporary. This API will be removed (renamed). */
VALUE rb_thread_io_blocking_region(rb_blocking_function_t *func, void *data1, int fd);