Skip to content

Commit a814ce9

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

4 files changed

Lines changed: 36 additions & 11 deletions

File tree

internal/io.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ struct rb_io {
131131
struct ccan_list_head blocking_operations;
132132
struct rb_execution_context_struct *closing_ec;
133133
VALUE wakeup_mutex;
134+
size_t closing_count;
134135

135136
// The fork generation of the blocking operations list.
136137
rb_serial_t fork_generation;

io.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5546,23 +5546,28 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl)
55465546
// Need to keep FILE objects of stdin, stdout and stderr, so we are done:
55475547
done = 1;
55485548
}
5549+
// The check for Qnil here is purely defensive, we should never invoke the fiber scheduler if there is no IO object.
5550+
else if (fptr->self != Qnil && fptr->closing_count == 0) {
5551+
VALUE scheduler = rb_fiber_scheduler_current();
5552+
if (scheduler != Qnil) {
5553+
fptr->closing_count += 1;
5554+
VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self);
5555+
fptr->closing_count -= 1;
5556+
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:
5568+
// Wait for blocking operations to ensure they do not hit EBADF:
55555569
rb_thread_io_close_wait(fptr);
55565570

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-
55665571
if (!done && stdio_file) {
55675572
// stdio_file is deallocated anyway even if fclose failed.
55685573
if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(error)) {
@@ -5724,10 +5729,12 @@ io_close_fptr(VALUE io)
57245729
if (!fptr) return 0;
57255730
if (fptr->fd < 0) return 0;
57265731

5732+
// This guards against multiple threads closing the same IO object:
57275733
if (rb_thread_io_close_interrupt(fptr)) {
57285734
/* calls close(fptr->fd): */
57295735
fptr_finalize_flush(fptr, FALSE, KEEPGVL);
57305736
}
5737+
57315738
rb_io_fptr_cleanup(fptr, FALSE);
57325739
return fptr;
57335740
}
@@ -8553,6 +8560,7 @@ rb_io_init_copy(VALUE dest, VALUE io)
85538560
ccan_list_head_init(&fptr->blocking_operations);
85548561
fptr->closing_ec = NULL;
85558562
fptr->wakeup_mutex = Qnil;
8563+
fptr->closing_count = 0;
85568564
fptr->fork_generation = GET_VM()->fork_gen;
85578565

85588566
if (!NIL_P(orig->pathv)) fptr->pathv = orig->pathv;
@@ -9283,6 +9291,7 @@ rb_io_open_descriptor(VALUE klass, int descriptor, int mode, VALUE path, VALUE t
92839291
ccan_list_head_init(&io->blocking_operations);
92849292
io->closing_ec = NULL;
92859293
io->wakeup_mutex = Qnil;
9294+
io->closing_count = 0;
92869295
io->fork_generation = GET_VM()->fork_gen;
92879296

92889297
if (encoding) {
@@ -9427,6 +9436,7 @@ rb_io_fptr_new(void)
94279436
ccan_list_head_init(&fp->blocking_operations);
94289437
fp->closing_ec = NULL;
94299438
fp->wakeup_mutex = Qnil;
9439+
fp->closing_count = 0;
94309440
fp->fork_generation = GET_VM()->fork_gen;
94319441
return fp;
94329442
}
@@ -9561,6 +9571,7 @@ io_initialize(VALUE io, VALUE fnum, VALUE vmode, VALUE opt)
95619571
ccan_list_head_init(&fp->blocking_operations);
95629572
fp->closing_ec = NULL;
95639573
fp->wakeup_mutex = Qnil;
9574+
fp->closing_count = 0;
95649575
fp->fork_generation = GET_VM()->fork_gen;
95659576
clear_codeconv(fp);
95669577
io_check_tty(fp);

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+
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)