Skip to content

net.Socket has no prependListener/prependOnceListener: calling them silently does nothing, so the listener never sees data (ioredis/iovalkey hang waiting for replies) #10441

Description

@proggeramlug

Found by the package audit (compiling real npm packages from source instead of Perry's native bindings) on
Perry e6dcb62 (v0.5.1587), Linux x64. On a net.Socket, prependListener and prependOnceListener read as undefined, but calling them does not throw. The call returns undefined, registers nothing, and the socket's 'data' never reaches the listener.

Reproduction

No dependencies. A local echo server is part of the script.

main.ts

import * as net from 'node:net';

const server = net.createServer((conn) => conn.on('data', (d) => conn.write(d)));  // echo
server.listen(0, '127.0.0.1', async () => {
  const port = (server.address() as net.AddressInfo).port;
  for (const method of ['on', 'prependListener', 'prependOnceListener']) {
    await new Promise<void>((resolve) => {
      const sock: any = net.createConnection({ host: '127.0.0.1', port });
      const timer = setTimeout(() => { console.log(method, '-> no data after 1s'); sock.destroy(); resolve(); }, 1000);
      sock.on('connect', () => {
        const ret = sock[method]('data', (d: Buffer) => {
          console.log(method, '-> data', JSON.stringify(d.toString()));
          clearTimeout(timer); sock.destroy(); resolve();
        });
        console.log(method, 'typeof:', typeof sock[method], 'returns socket:', ret === sock, 'listenerCount:', sock.listenerCount('data'));
        sock.resume();   // prepend*Listener does not start flowing mode by itself
        sock.write('ping');
      });
    });
  }
  server.close();
});
node main.ts
perry compile main.ts -o out && ./out

Expected (Node 26.5.1)

on typeof: function returns socket: true listenerCount: 1
on -> data "ping"
prependListener typeof: function returns socket: true listenerCount: 1
prependListener -> data "ping"
prependOnceListener typeof: function returns socket: true listenerCount: 1
prependOnceListener -> data "ping"

Actual (Perry)

Same output with and without PERRY_NO_AUTO_OPTIMIZE=1:

on typeof: function returns socket: true listenerCount: 1
on -> data "ping"
prependListener typeof: undefined returns socket: false listenerCount: 0
prependListener -> no data after 1s
prependOnceListener typeof: undefined returns socket: false listenerCount: 0
prependOnceListener -> no data after 1s

The static form sock.prependListener('data', fn) behaves the same way. Calling a method that does not exist at all, such as sock.noSuchMethod('x'), also returns undefined. Node throws TypeError: sock.noSuchMethod is not a function. So a missing socket method shows up as a hang, not as an error.

Impact

  • ioredis 5.11.1: built/DataHandler.js:25 attaches the RESP parser with
    redis.stream.prependListener("data", (data) => parser.execute(data)) and then calls redis.stream.resume(). Under Perry the parser
    never gets a byte. The client connects and writes CLIENT SETINFO (the server receives it), but no reply ever resolves, so
    ready never fires and every command promise hangs.
  • iovalkey 0.4.0: same code at built/DataHandler.js:24.
  • If that line is changed to redis.stream.on("data", …), the audit's full ioredis test matches Node byte for byte. The same is true for iovalkey.
    The test covers strings with EX/ttl, incr/mget, hash, list, set, zset, pipeline, multi, eval, WRONGTYPE error, getBuffer, keyPrefix, pub/sub and quit.
    This was the last Perry defect on that path after the workarounds below.
  • Any other stream consumer that uses prependListener on a socket is likely affected (inferred).

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions