The issue involves handling UV_EALREADY in socket operations, requiring careful changes to avoid unintended destruction.
The issue occurs when `tryReadStart()` destroys a socket upon receiving `UV_EALREADY` from `readStart()`, which should be ignored instead. This happens in specific scenarios involving multiple `readStart` calls in the same microtask batch. The fix requires modifying `tryReadStart()` to handle `UV_EALREADY` appropriately without destroying the socket.
v26.0.0-pre (main branch, also affects v24.12.0)
Linux 6.12.73+deb13-amd64 x86_64
net
tryReadStart() in lib/net.js destroys the socket when readStart() returns UV_EALREADY. This can be reproduced by calling readStart when reading is already active at the libuv level:
const net = require('net');
const { internalBinding } = require('internal/test/binding');
const { UV_EALREADY } = internalBinding('uv');
const server = net.createServer((conn) => { conn.on('error', () => {}); });
server.listen('/tmp/test-ealready.sock', () => {
const socket = net.createConnection({ path: '/tmp/test-ealready.sock' });
socket.on('connect', () => {
// Simulate readStart returning UV_EALREADY (happens with sockets
// opened via uv_pipe_open where reading is already active)
socket._handle.readStart = () => UV_EALREADY;
socket._handle.reading = false;
socket._read(16384);
// Socket is now destroyed even though reading was already active
console.log('destroyed:', socket.destroyed); // true
socket.destroy();
});
socket.on('error', (e) => console.log('error:', e.code)); // EALREADY
socket.on('close', () => server.close());
});
In practice, this happens when socket types with no async connect phase (synchronous bind, 'connect' emitted via nextTick) cause multiple readStart calls in the same microtask batch. Discovered while implementing AF_CAN socket support (#62398), but the bug is in tryReadStart itself.
100% reproducible. The condition is readStart() returning UV_EALREADY, which occurs when:
uv_pipe_open() and readStart is called more than once_read callback (queued while connecting=true) and the stream resume() from a 'data' listener both fire in the same tickClaim this issue to let others know you're working on it. You'll earn 20 points when you complete it!