Uh oh!
There was an error while loading. Please reload this page.
Tear down the connection when session setup fails in Connection.open() - #20
Conversation
After the TCP connection is established (createConnection + createClient) and its 'error'/'close' listeners are registered, if openSession() or requestStatementId() rejects (bad credentials, timeout, or a non-200 status), the catch only logged the error and rethrew — leaking the open socket and its listeners. In the pool this propagates through init() with no close(), so each failed connect attempt leaks a socket. Mirror close()'s teardown (removeAllListeners + destroy/end + null the refs + isConnected=false) in the catch before rethrowing, and add a regression test. Signed-off-by: Zihan Dai <99155080+PDGGK@users.noreply.github.com>
CritasWang
left a comment
There was a problem hiding this comment.
Verified the leak on develop (21b9432): the old catch only logged and rethrew, and neither Session.open() nor the pools' createPoolSession() do any fallback cleanup, so every failed session setup (bad credentials, timeout, non-200) really did strand a live socket plus its 'error'/'close' listeners.
The fix checks out on the edge cases I probed:
- Socket not yet created (host/port validation or
createConnectionthrowing): theif (this.connection)guard skips cleanly and just rethrows. ✅ - Double-teardown safety: the registered
'error'/'close'listeners only flipisConnected, they never destroy, andsocket.destroy()is idempotent — no double-destroy hazard. ✅ - Timers: the 30s timeouts inside
openSession/requestStatementIdclear themselves in their callbacks, and Connection has no heartbeat/interval, so nothing else needs cleanup here. ✅
The regression test is valid — the base catch never calls removeAllListeners/destroy, so it fails on develop and passes here.
Two low-severity suggestions inline (stale sessionId not reset, and guarding the teardown so it can't mask the original error). One style note, no change requested: this teardown block now exists in three places (close() happy path, close() catch, open() catch) — a private teardownConnection() helper would be a nice follow-up, either here or later.
Looks good to merge with or without the inline tweaks.
| // Tear down the half-open connection so its socket and event listeners | ||
| // don't leak when session setup (openSession/requestStatementId) fails | ||
| // after the TCP connection was already established. Mirrors close(). | ||
| if (this.connection) { | ||
| this.connection.removeAllListeners(); | ||
| if (typeof this.connection.destroy === "function") { | ||
| this.connection.destroy(); | ||
| } else { | ||
| this.connection.end(); | ||
| } | ||
| this.connection = null; | ||
| } | ||
| this.client = null; | ||
| this.isConnected = false; |
There was a problem hiding this comment.
Two small things to fully "mirror close()" here:
close()also nullssessionId(andstatementId), but this catch doesn't. IfopenSessionsucceeds and thenrequestStatementIdfails, the object keeps a stalesessionId— harmless forisOpen()/re-close(), butgetSessionId()would still return the dead session's id. Worth resetting both here for consistency.The teardown itself isn't guarded: if
removeAllListeners()/destroy()ever throws, it would replace the original error (e.g. the auth failure) as the propagated one. Wrapping the teardown in atry { … } catch { /* log */ }keeps thethrow errorbelow always rethrowing the real cause.close()has an outer catch that plays this role; this path doesn't.
| }; | ||
| const connection = new Connection(config); | ||
| await expect(connection.open()).rejects.toThrow(); |
There was a problem hiding this comment.
Nit: consider asserting the original error surfaces, e.g. .rejects.toThrow("auth failed"). That pins down that the new teardown never masks the real failure cause (see the comment on the source side), and makes the regression test a bit stronger for free.
Address review feedback on apache#20: on a failed session setup the open() catch now (1) clears sessionId/statementId so getSessionId() cannot return a stale id after openSession succeeds but requestStatementId fails, and (2) wraps the socket teardown in its own try/catch so a cleanup failure cannot mask the original error that is rethrown below. Strengthen the regression tests: assert the original setup error surfaces rather than a teardown error, and add a case asserting that a failure after openSession clears the session id. Signed-off-by: Zihan Dai <99155080+PDGGK@users.noreply.github.com>
PDGGK
commented
Jul 23, 2026
Thanks for the thorough review @CritasWang — applied both inline suggestions:
Tests: strengthened the existing case to assert the original error surfaces ( On the |
Uh oh!
There was an error while loading. Please reload this page.
This is the connection-leak PR (finding 6) from the dev@ discussion "[DISCUSS] Hardening the iotdb-client-nodejs session pool / connection lifecycle".
Problem
Connection.open()establishes the TCP connection (createConnection + createClient) and registers its 'error'/'close' listeners, then performs session setup (openSession, requestStatementId). If session setup rejects (bad credentials, a timeout, or a non-200 status), the catch only logged and rethrew — leaking the open socket and its listeners. Through the pool this propagates out ofinit()with noclose(), so every failed connect attempt leaks one socket.Fix
Mirror
close()'s teardown in the catch before rethrowing:removeAllListeners, destroy/end the socket, null the refs, setisConnected = false.Tests
Added a regression test asserting teardown when
openSessionfails after the TCP connect. Fulltests/unitsuite green (155 tests).