Follow-up from #1358 (fixed in #1367).
When a connection that uses an SSH tunnel is cancelled while connecting, the tunnel created during buildEffectiveConnection can stay open if the user does not retry:
With no retry, the tunnel's local forward port and keep-alive task stay alive until that connection is opened and closed again. Low frequency, but a real resource leak.
Repro
- Configure a Postgres (or any) connection over an SSH tunnel to an unreachable host.
- Connect, then cancel while it is connecting.
- Do not retry. The SSH tunnel (local forward port + keep-alive task) remains until the connection is later opened and closed.
Proposed fix
Close the tunnel in cancelEnsureConnected when the cancelled session has no driver. The subtle part is the cancel-then-retry race: a retry's createTunnel replaces the tunnel for the same connectionId, so the cancel path must not close a newer tunnel. cancelEnsureConnected and a retry's connectToSession both run on @MainActor but have await suspension points, so interleaving has to be handled.
Notes
Follow-up from #1358 (fixed in #1367).
When a connection that uses an SSH tunnel is cancelled while connecting, the tunnel created during
buildEffectiveConnectioncan stay open if the user does not retry:connectToSessionresumes and, on the cancelled path, only disconnects its own driver. It deliberately does not close the connectionId-keyed tunnel, so it cannot tear down a retry's tunnel (this was intentional in fix(connections): cancelled connect no longer aborts a later successful connection (#1358) #1367).cancelEnsureConnectedcancels the connect task and removes the session entry, but never closes the tunnel.With no retry, the tunnel's local forward port and keep-alive task stay alive until that connection is opened and closed again. Low frequency, but a real resource leak.
Repro
Proposed fix
Close the tunnel in
cancelEnsureConnectedwhen the cancelled session has no driver. The subtle part is the cancel-then-retry race: a retry'screateTunnelreplaces the tunnel for the same connectionId, so the cancel path must not close a newer tunnel.cancelEnsureConnectedand a retry'sconnectToSessionboth run on@MainActorbut haveawaitsuspension points, so interleaving has to be handled.Notes
LibSSH2Tunnel.close()already tears down cleanly and idempotently.