diff --git a/internal/vm/io.go b/internal/vm/io.go index b142aea4..8448297f 100644 --- a/internal/vm/io.go +++ b/internal/vm/io.go @@ -299,7 +299,14 @@ func (vm *VM) registerIO() { // before its require. The featureHooks map is already created by registerPrime, // which runs first. vm.featureHooks["fcntl"] = vm.installFcntl - vm.featureHooks["io/nonblock"] = func() { installIONonblock(cIO) } + // ⛔ The sockets are installed from the SAME hook, not from their own + // registration: one place decides what `require "io/nonblock"` adds, so the + // two cannot drift apart. The socket classes exist from startup, so they are + // already there when this fires. + vm.featureHooks["io/nonblock"] = func() { + installIONonblock(cIO) + vm.installSocketNonblock() + } // IO::WaitReadable / IO::WaitWritable (io.c Init_IO) are the marker modules a // non-blocking read or write raises with: IO::EAGAINWaitReadable is an // Errno::EAGAIN subclass that includes IO::WaitReadable, so `rescue diff --git a/internal/vm/socket.go b/internal/vm/socket.go index 1b6a3974..3fb0fc0e 100644 --- a/internal/vm/socket.go +++ b/internal/vm/socket.go @@ -109,6 +109,78 @@ func (vm *VM) registerSocket() { // registerTCPSocket installs TCPSocket.new plus the connected-socket instance // surface (read/gets/readpartial/write/print/puts/<