-
- Notifications
You must be signed in to change notification settings - Fork 35.2k
Description
Version
v26.0.0-pre (main branch, also affects v24.12.0)
Platform
Linux 6.12.73+deb13-amd64 x86_64 Subsystem
net
What steps will reproduce the bug?
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.
How often does it reproduce? Is there a required condition?
100% reproducible. The condition is readStart() returning UV_EALREADY, which occurs when:
- A socket fd is opened via
uv_pipe_open()andreadStartis called more than once - The deferred
_readcallback (queued whileconnecting=true) and the streamresume()from a'data'listener both fire in the same tick
What is the expected behavior? Why is that the expected behavior?
UV_EALREADY means "reading is already in progress." The socket should continue operating normally. tryReadStart should be idempotent — if reading is already active, there's nothing to do.
What do you see instead?
The socket is destroyed with Error: read EALREADY. The 'error' event fires and the socket becomes unusable
Additional information
Fix with regression test: https://github.com/dirkwa/node/tree/fix-tryreadstart-ealready
The fix adds a guard for UV_EALREADY in tryReadStart, making it ignore the "already reading" case instead of destroying the socket. This matches the intent of the existing JS-side reading flag guard in Socket._read.
Refs: #62398