diff options
-rw-r--r-- | internal/thread.h | 1 | ||||
-rw-r--r-- | test/fiber/test_io.rb | 43 | ||||
-rw-r--r-- | thread.c | 8 |
3 files changed, 51 insertions, 1 deletions
diff --git a/internal/thread.h b/internal/thread.h index a2926febc3..e079ebb22b 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -60,6 +60,7 @@ int rb_thread_wait_for_single_fd(int fd, int events, struct timeval * timeout); struct rb_io_close_wait_list { struct ccan_list_head pending_fd_users; VALUE closing_thread; + VALUE closing_fiber; VALUE wakeup_mutex; }; int rb_notify_fd_close(int fd, struct rb_io_close_wait_list *busy); diff --git a/test/fiber/test_io.rb b/test/fiber/test_io.rb index 0e3e086d5a..7973399acb 100644 --- a/test/fiber/test_io.rb +++ b/test/fiber/test_io.rb @@ -234,4 +234,47 @@ class TestFiberIO < Test::Unit::TestCase assert_equal "ok\n", result end + + # Tests for https://bugs.ruby-lang.org/issues/20723 which would + # otherwise deadlock this test. + def test_close_while_reading_on_thread + # Windows has UNIXSocket, but only with VS 2019+ + omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) + + i, o = Socket.pair(:UNIX, :STREAM) + if RUBY_PLATFORM=~/mswin|mingw/ + i.nonblock = true + o.nonblock = true + end + + message = nil + + reading_thread = Thread.new do + Thread.current.report_on_exception = false + i.wait_readable + end + + fs_thread = Thread.new do + # Wait until the reading thread is blocked on read: + Thread.pass until reading_thread.status == "sleep" + + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + i.close + end + end + + assert_raise(IOError) { reading_thread.join } + refute_nil fs_thread.join(5), "expected thread to terminate within 5 seconds" + + assert_predicate(i, :closed?) + ensure + fs_thread&.kill + fs_thread&.join rescue nil + reading_thread&.kill + reading_thread&.join rescue nil + i&.close + o&.close + end end @@ -1698,7 +1698,12 @@ thread_io_wake_pending_closer(struct waiting_fd *wfd) RB_VM_LOCK_LEAVE(); if (has_waiter) { - rb_thread_wakeup(wfd->busy->closing_thread); + rb_thread_t *th = rb_thread_ptr(wfd->busy->closing_thread); + if (th->scheduler != Qnil) { + rb_fiber_scheduler_unblock(th->scheduler, wfd->busy->closing_thread, wfd->busy->closing_fiber); + } else { + rb_thread_wakeup(wfd->busy->closing_thread); + } rb_mutex_unlock(wfd->busy->wakeup_mutex); } } @@ -2625,6 +2630,7 @@ rb_notify_fd_close(int fd, struct rb_io_close_wait_list *busy) has_any = !ccan_list_empty(&busy->pending_fd_users); busy->closing_thread = rb_thread_current(); + busy->closing_fiber = rb_fiber_current(); wakeup_mutex = Qnil; if (has_any) { wakeup_mutex = rb_mutex_new(); |