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
46 lines
1.6 KiB
TypeScript
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)
|
|
})
|