@@ -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 )) {
0 commit comments