Hi — I wanted to share a heads-up about a change to the optional Fiber::Scheduler buffered I/O hooks in Ruby 4.1. It was merged into CRuby's default branch on August 25, 2026.
While checking scheduler implementations against ruby-head, I noticed that lib/rage/fiber_scheduler.rb uses the previous (length, offset) order. The read hook can pause and continue reading, while the write hook passes the whole buffer string together with the old length/offset interpretation, so both paths may behave differently under the new single-transfer contract.
References
Contract change
| Hook |
Ruby 4.0 and earlier |
Ruby 4.1 |
io_read |
io_read(io, buffer, length, offset) |
io_read(io, buffer, offset, length) |
io_write |
io_write(io, buffer, length, offset) |
io_write(io, buffer, offset, length) |
Under scheduler interface v4, length is a maximum for one operation. Return a short result directly, return 0 for zero length, and on a readiness backend return -EAGAIN instead of waiting or retrying inside the hook.
One possible update for Rage
defined?(IO::Buffer::VERSION) && IO::Buffer::VERSION >= 3 could select v4 versus legacy hook definitions.
- The v4 read path could make one bounded
Iodine::Scheduler.read request for at most length bytes, copy a successful result to buffer at the requested buffer offset, and return the string's byte size.
- One detail worth checking is Iodine's third argument: the current code passes the buffer offset into
Iodine::Scheduler.read, but the Ruby hook's offset refers only to the position within IO::Buffer. I may be missing an Iodine-specific convention here.
- Iodine's would-block result could return
-Errno::EAGAIN::Errno directly, without the v4 loop or Fiber.pause retry.
- The v4 write path could extract exactly
buffer.get_string(offset, length) and perform one write, passing a separate I/O offset only if the Iodine API requires one.
- A zero-length request could return
0 before calling Iodine, while the existing implementation remains available in the older-interface branch.
The public C/native version gate is RUBY_FIBER_SCHEDULER_VERSION >= 4; IO::Buffer::VERSION >= 3 is the corresponding Ruby-level check.
If this reading of the Iodine API is correct, I would be happy to help prepare or test a patch. Useful Ruby-head cases would use different nonzero offset and length values, plus short-transfer, would-block, and zero-length operations. If Ruby 4.1 is not currently in scope, please feel free to close this.
Hi — I wanted to share a heads-up about a change to the optional
Fiber::Schedulerbuffered I/O hooks in Ruby 4.1. It was merged into CRuby's default branch on August 25, 2026.While checking scheduler implementations against ruby-head, I noticed that
lib/rage/fiber_scheduler.rbuses the previous(length, offset)order. The read hook can pause and continue reading, while the write hook passes the whole buffer string together with the old length/offset interpretation, so both paths may behave differently under the new single-transfer contract.References
io-eventcompatibility implementation: socketry/io-event#211io-eventasynchronousio_uringfollow-up: socketry/io-event#213Contract change
io_readio_read(io, buffer, length, offset)io_read(io, buffer, offset, length)io_writeio_write(io, buffer, length, offset)io_write(io, buffer, offset, length)Under scheduler interface v4,
lengthis a maximum for one operation. Return a short result directly, return0for zero length, and on a readiness backend return-EAGAINinstead of waiting or retrying inside the hook.One possible update for Rage
defined?(IO::Buffer::VERSION) && IO::Buffer::VERSION >= 3could select v4 versus legacy hook definitions.Iodine::Scheduler.readrequest for at mostlengthbytes, copy a successful result tobufferat the requested bufferoffset, and return the string's byte size.Iodine::Scheduler.read, but the Ruby hook'soffsetrefers only to the position withinIO::Buffer. I may be missing an Iodine-specific convention here.-Errno::EAGAIN::Errnodirectly, without the v4 loop orFiber.pauseretry.buffer.get_string(offset, length)and perform one write, passing a separate I/O offset only if the Iodine API requires one.0before calling Iodine, while the existing implementation remains available in the older-interface branch.The public C/native version gate is
RUBY_FIBER_SCHEDULER_VERSION >= 4;IO::Buffer::VERSION >= 3is the corresponding Ruby-level check.If this reading of the Iodine API is correct, I would be happy to help prepare or test a patch. Useful Ruby-head cases would use different nonzero offset and length values, plus short-transfer, would-block, and zero-length operations. If Ruby 4.1 is not currently in scope, please feel free to close this.