Skip to content

Commit ccecac7

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

3 files changed

Lines changed: 31 additions & 13 deletions

File tree

io.c

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5540,29 +5540,31 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl)
55405540
}
55415541
}
55425542

5543+
// Wait for blocking operations to ensure they do not hit EBADF:
5544+
rb_thread_io_close_wait(fptr);
5545+
55435546
int done = 0;
55445547

55455548
if (RUBY_IO_EXTERNAL_P(fptr) || fd <= 2) {
55465549
// Need to keep FILE objects of stdin, stdout and stderr, so we are done:
55475550
done = 1;
55485551
}
5552+
// The check for Qnil here is purely defensive, we should never invoke the fiber scheduler if there is no IO object.
5553+
else if (fptr->self != Qnil) {
5554+
VALUE scheduler = rb_fiber_scheduler_current();
5555+
if (scheduler != Qnil) {
5556+
VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self);
5557+
if (!UNDEF_P(result)) {
5558+
// Scheduler handled it:
5559+
if (fptr->fd == -1) return;
5560+
}
5561+
}
5562+
}
55495563

55505564
fptr->fd = -1;
55515565
fptr->stdio_file = 0;
55525566
fptr->mode &= ~(FMODE_READABLE|FMODE_WRITABLE);
55535567

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-
55665568
if (!done && stdio_file) {
55675569
// stdio_file is deallocated anyway even if fclose failed.
55685570
if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(error)) {
@@ -5724,10 +5726,12 @@ io_close_fptr(VALUE io)
57245726
if (!fptr) return 0;
57255727
if (fptr->fd < 0) return 0;
57265728

5729+
// This guards against multiple threads closing the same IO object:
57275730
if (rb_thread_io_close_interrupt(fptr)) {
57285731
/* calls close(fptr->fd): */
57295732
fptr_finalize_flush(fptr, FALSE, KEEPGVL);
57305733
}
5734+
57315735
rb_io_fptr_cleanup(fptr, FALSE);
57325736
return fptr;
57335737
}
@@ -5743,6 +5747,7 @@ fptr_waitpid(rb_io_t *fptr, int nohang)
57435747
}
57445748
}
57455749

5750+
57465751
VALUE
57475752
rb_io_close(VALUE io)
57485753
{

test/fiber/scheduler.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ def io_select(...)
255255
end.value
256256
end
257257

258+
# This hook is invoked by `IO#close`. Using a separate IO object
259+
# demonstrates that the close operation is asynchronous.
260+
def io_close(io)
261+
Fiber.blocking{io.close}
262+
end
263+
258264
# This hook is invoked by `Kernel#sleep` and `Thread::Mutex#sleep`.
259265
def kernel_sleep(duration = nil)
260266
# $stderr.puts [__method__, duration, Fiber.current].inspect

thread.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,14 @@ 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+
if (io->closing_ec || io->fd == -1) {
1778+
rb_execution_context_t *ec = GET_EC();
1779+
rb_vm_t *vm = ec->thread_ptr->vm;
1780+
VALUE error = vm->special_exceptions[ruby_error_stream_closed];
1781+
rb_exc_raise(error);
1782+
}
1783+
17761784
ccan_list_add(rb_io_blocking_operations(io), &blocking_operation->list);
17771785
}
17781786

@@ -2895,7 +2903,6 @@ rb_thread_io_close_wait(struct rb_io* io)
28952903

28962904
// We are done closing:
28972905
io->wakeup_mutex = Qnil;
2898-
io->closing_ec = NULL;
28992906
}
29002907

29012908
void

0 commit comments

Comments
 (0)