Skip to content

Commit 8b16068

Browse files
Re-introduce support for io_close hook.
1 parent 588347a commit 8b16068

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

io.c

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5547,22 +5547,28 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl)
55475547
done = 1;
55485548
}
55495549

5550+
// Wait for blocking operations to be interrupted and cleaned up BEFORE calling
5551+
// the scheduler hook. This ensures that if the scheduler closes the fd synchronously
5552+
// (or schedules an async close that completes quickly), blocking operations won't
5553+
// fail with EBADF.
5554+
rb_thread_io_close_wait(fptr);
5555+
5556+
// Call scheduler hook BEFORE setting fd = -1, so scheduler can access the fd
5557+
// via rb_io_descriptor if needed (e.g., for async close operations like io_uring_prep_close).
5558+
// The fd is still valid at this point. If the scheduler returns a truthy value,
5559+
// it will handle closing the fd asynchronously, so we skip the synchronous close below.
5560+
if (!done && fd >= 0) {
5561+
VALUE scheduler = rb_fiber_scheduler_current();
5562+
if (scheduler != Qnil) {
5563+
VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self);
5564+
if (!UNDEF_P(result)) done = 1;
5565+
}
5566+
}
5567+
55505568
fptr->fd = -1;
55515569
fptr->stdio_file = 0;
55525570
fptr->mode &= ~(FMODE_READABLE|FMODE_WRITABLE);
55535571

5554-
// wait for blocking operations to ensure they do not hit EBADF:
5555-
rb_thread_io_close_wait(fptr);
5556-
5557-
// Disable for now.
5558-
// if (!done && fd >= 0) {
5559-
// VALUE scheduler = rb_fiber_scheduler_current();
5560-
// if (scheduler != Qnil) {
5561-
// VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self);
5562-
// if (!UNDEF_P(result)) done = 1;
5563-
// }
5564-
// }
5565-
55665572
if (!done && stdio_file) {
55675573
// stdio_file is deallocated anyway even if fclose failed.
55685574
if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(error)) {

thread.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,15 @@ rb_io_blocking_operations(struct rb_io *io)
17731773
static void
17741774
rb_io_blocking_operation_enter(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation)
17751775
{
1776+
// Prevent new blocking operations from starting if the IO is being closed.
1777+
// closing_ec is set when closing starts and cleared after fd = -1 is set.
1778+
if (io->closing_ec) {
1779+
rb_execution_context_t *ec = GET_EC();
1780+
rb_vm_t *vm = ec->thread_ptr->vm;
1781+
VALUE error = vm->special_exceptions[ruby_error_stream_closed];
1782+
rb_exc_raise(error);
1783+
}
1784+
17761785
ccan_list_add(rb_io_blocking_operations(io), &blocking_operation->list);
17771786
}
17781787

@@ -2893,9 +2902,11 @@ rb_thread_io_close_wait(struct rb_io* io)
28932902
}
28942903
rb_mutex_unlock(wakeup_mutex);
28952904

2896-
// We are done closing:
2905+
// We are done waiting for blocking operations:
28972906
io->wakeup_mutex = Qnil;
2898-
io->closing_ec = NULL;
2907+
// Note: closing_ec is NOT cleared here. Once fd = -1 is set, rb_thread_io_close_interrupt
2908+
// won't be called again (due to fd check), and new blocking operations will fail at
2909+
// rb_io_check_closed (due to fd check), so closing_ec becomes redundant but harmless.
28992910
}
29002911

29012912
void

0 commit comments

Comments
 (0)