summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/thread.h1
-rw-r--r--test/fiber/test_io.rb43
-rw-r--r--thread.c8
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
diff --git a/thread.c b/thread.c
index 5a79200785..2a937ca278 100644
--- a/thread.c
+++ b/thread.c
@@ -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();