Files
hermes-agent/apps/desktop/electron/ssh-control-master-holders.test.ts
Austin Pickett 05d832d685 fix(desktop): only the last holder exits a shared SSH ControlMaster
Every SshConnection for one scope/host/identity hashes to the same
ControlPath, and ControlMaster=auto attaches later connections to the
master already listening there. A stale bootstrap attempt (timed out,
superseded, rolled back) that closed late ran `ssh -O exit` on that
socket and tore down the master its successor had attached to, killing
the live backend's forward after "Remote Hermes backend is ready".

Connections now claim their ControlPath from the moment open() starts
dialing and release it on close or failed open; close() runs -O exit
(and the socket-unlink fallback) only when no other connection still
holds the socket. No-mux (Windows) is unchanged.

Covers the stale-attempt teardown half of #97264; the hardcoded 45s
ready timeout is untouched (tracked in #94642 / #94665).

Refs #97264
2026-09-28 20:21:57 -04:00

46 lines
1.6 KiB
TypeScript

import assert from 'node:assert/strict'
import { test } from 'vitest'
import { controlMasterCloseAction, createControlMasterHolders } from './ssh-control-master-holders'
test('controlMasterCloseAction exits the master only for the last holder', () => {
assert.equal(controlMasterCloseAction(0), 'exit-master')
assert.equal(controlMasterCloseAction(1), 'release-only')
assert.equal(controlMasterCloseAction(3), 'release-only')
})
test('holders are counted per ControlPath and released in any order', () => {
const holders = createControlMasterHolders()
const older = {}
const newer = {}
const elsewhere = {}
holders.acquire('/s/a.sock', older)
holders.acquire('/s/a.sock', newer)
holders.acquire('/s/b.sock', elsewhere)
assert.equal(holders.release('/s/a.sock', older), 'release-only', 'older closer leaves the newer holder its master')
assert.equal(holders.release('/s/a.sock', newer), 'exit-master')
assert.equal(holders.count('/s/a.sock'), 0)
assert.equal(holders.count('/s/b.sock'), 1, 'other sockets are unaffected')
})
test('re-acquiring and double-releasing are idempotent', () => {
const holders = createControlMasterHolders()
const conn = {}
holders.acquire('/s/a.sock', conn)
holders.acquire('/s/a.sock', conn)
assert.equal(holders.count('/s/a.sock'), 1)
assert.equal(holders.release('/s/a.sock', conn), 'exit-master')
assert.equal(holders.release('/s/a.sock', conn), 'exit-master', 'releasing an unknown holder never blocks a close')
})
test('an empty ControlPath (no-mux) is never tracked', () => {
const holders = createControlMasterHolders()
holders.acquire('', {})
assert.equal(holders.count(''), 0)
})