connect timeout does not cover the STOMP handshake — a stalled CONNECT leaks the socket forever
Summary
stompit.connect(options, cb) accepts a timeout option (default 3000 ms), but that timeout only covers the transport connect (TCP / TLS). It is cleared the moment the transport connects, before the STOMP CONNECT→CONNECTED handshake runs. As a result, if a server accepts the TCP/TLS socket but never replies with a CONNECTED frame (e.g. an overloaded broker), the returned promise/callback never fires, no timeout ever triggers, and the underlying socket stays open forever. Because connect() returns the client synchronously but the callback never runs, a caller using only the callback has no handle to the leaked socket.
Where
lib/connect.js:
- The single
timeout is armed at socket = transportConnect(...) (covers transport).
onConnected() (fires on transport connect) calls cleanup(), which does clearTimeout(timeout) — canceling the timeout — and only then calls client.connect(connectOpts, connectListener) to perform the STOMP handshake.
client.connect waits for CONNECTED with no timeout. Heartbeats are only armed after CONNECTED is received, so they don't help either.
Impact
Any consumer that keeps connecting (especially a long-running service with reconnect logic) against a broker that stalls the STOMP handshake will accumulate half-open sockets. In our case a long-running consumer facing an overloaded ActiveMQ ended up with 10,000+ simultaneous connections, saturating the broker. Even a simple client just hangs indefinitely instead of getting a connect timeout.
Expected
The timeout option should bound the entire connect, including the STOMP handshake — a stalled CONNECT should time out, client.destroy(...) the socket, and surface an error to the callback.
Repro (conceptual)
Point stompit.connect at a TCP server that accepts the socket and never sends a STOMP CONNECTED frame. Observe: the callback never fires and the socket remains open past timeout.
Fix
Keep the connect timeout armed until the STOMP handshake completes (or errors), instead of clearing it when the transport connects. PR attached.
connect
timeoutdoes not cover the STOMP handshake — a stalled CONNECT leaks the socket foreverSummary
stompit.connect(options, cb)accepts atimeoutoption (default 3000 ms), but that timeout only covers the transport connect (TCP / TLS). It is cleared the moment the transport connects, before the STOMPCONNECT→CONNECTEDhandshake runs. As a result, if a server accepts the TCP/TLS socket but never replies with aCONNECTEDframe (e.g. an overloaded broker), the returned promise/callback never fires, no timeout ever triggers, and the underlying socket stays open forever. Becauseconnect()returns the client synchronously but the callback never runs, a caller using only the callback has no handle to the leaked socket.Where
lib/connect.js:timeoutis armed atsocket = transportConnect(...)(covers transport).onConnected()(fires on transport connect) callscleanup(), which doesclearTimeout(timeout)— canceling the timeout — and only then callsclient.connect(connectOpts, connectListener)to perform the STOMP handshake.client.connectwaits forCONNECTEDwith no timeout. Heartbeats are only armed afterCONNECTEDis received, so they don't help either.Impact
Any consumer that keeps connecting (especially a long-running service with reconnect logic) against a broker that stalls the STOMP handshake will accumulate half-open sockets. In our case a long-running consumer facing an overloaded ActiveMQ ended up with 10,000+ simultaneous connections, saturating the broker. Even a simple client just hangs indefinitely instead of getting a connect timeout.
Expected
The
timeoutoption should bound the entire connect, including the STOMP handshake — a stalledCONNECTshould time out,client.destroy(...)the socket, and surface an error to the callback.Repro (conceptual)
Point
stompit.connectat a TCP server that accepts the socket and never sends a STOMPCONNECTEDframe. Observe: the callback never fires and the socket remains open pasttimeout.Fix
Keep the connect
timeoutarmed until the STOMP handshake completes (or errors), instead of clearing it when the transport connects. PR attached.