Repository navigation
redis@6.1.0 connect() hangs: events.once(socket, 'connect') inside @redis/client registers no listener and never resolves #11196
Description
Summary
On current origin/main (8cfc6455f, which includes #11173 and #11137), redis@6.1.0's client.connect() never completes. The TCP connection opens: redis-server shows the client and ss shows ESTAB. But nothing is ever sent (no HELLO in MONITOR), and neither the connect nor the ready event fires. This happens with createClient({ socket: { host, port } }) and with createClient({ url }). On 2026-09-23, #11170's reporter got through connect / ready / HELLO on an older main plus #11150's fix, so this is a newer stall, earlier in the connect path.
Where it stops
I added trace prints to @redis/client/dist/lib/client/socket.js (RedisSocket#createSocket) in a local copy:
const socket = this.#socketFactory.create(); // net.createConnection(withDefaults)
socket.on('connect', () => console.log('raw connect')); // added
const p = (0, node_events_1.once)(socket, 'connect'); // node_events_1 = require("node:events")
console.log('listeners now', socket.listenerCount('connect'));
p.then(() => console.log('once resolved'));
await p;
console.log('after await once');Perry output:
listeners now 1 <- only the raw listener; once() registered nothing
raw connect <- the socket DOES emit 'connect'
(1s later) connecting=false readyState=open listeners=1
once resolved and after await once never print. events.once(socket, 'connect') returns a promise, but it attaches no connect listener to the socket, so the promise never settles and connect() hangs forever.
What does NOT reproduce it
Each of these connects fine on the same build:
- A package-free
net.createConnection+await once(s, "connect"), including redis's exact options (noDelay,keepAlive,keepAliveInitialDelay,timeout: undefined,onread: undefined,readable/writable: true). - A
compilePackagesCJS package that mirrorsRedisSocket:__importDefault(require("node:net")), a private#createSocketFactory,setTimeout(connectTimeout), thedo { try { await this.#createSocket() … } catch … } whileloop, and extra loads ofnode:tls/ barenet/dns/promises/diagnostics_channel.
So something in the full @redis/client module graph changes what events.once does for this socket. One candidate is a module patching events / EventEmitter, or once taking its EventTarget branch. I have not isolated it further.
Repro
- Linux x86_64, perry-dev build of
8cfc6455f,PERRY_NO_AUTO_OPTIMIZE=1, withlibperry_ext_net.a+libperry_ext_events.ainPERRY_RUNTIME_DIR(the compile routesnet/eventsto those wrappers). npm install redis@6.1.0(printsredis version: 6.1.0), withcompilePackages= redis, @redis/client, @redis/bloom, @redis/json, @redis/search, @redis/time-series, cluster-key-slot.- redis-server 7.0.15.
const { createClient } = require("redis");
console.log("redis version:", require("redis/package.json").version);
async function main() {
const client = createClient({ socket: { host: "127.0.0.1", port: 6379 } });
client.on("error", (e: any) => console.log("error event:", e && e.message));
client.on("connect", () => console.log("event connect"));
client.on("ready", () => console.log("event ready"));
await client.connect();
console.log("SET ->", await client.set("key", "value"));
console.log("GET ->", await client.get("key"));
await client.quit();
}
main();
setTimeout(() => { console.log("TIMEOUT"); process.exit(3); }, 6000);Node 26.5.1: event connect / event ready / SET -> OK / GET -> value. Perry: only the version line, then TIMEOUT.
This is now the redis@6.1.0 blocker. #11170 (PR #11192) fixes the next failure after it: the first command's private-field read in *[Symbol.iterator]().
Activity
fix(stdlib,ffi): common handle ids come from the shared FFI pool so t…
fix(stdlib,ffi): common handle ids come from the shared FFI pool so t…