fix(ssh): retry with the saved Node.js mirror once a running connect fails the same way (#31089)

* fix(ssh): retry with the saved mirror once a connect already dialing fails

* fix(ssh): note why the status is current when the lock clears
This commit is contained in:
Neil
2026-10-20 03:06:27 -07:00
committed by GitHub
parent 917c577433
commit 956a6aadcb
2 changed files with 73 additions and 9 deletions
@@ -3,12 +3,25 @@ import {
NODE_DIST_MIRROR_SUGGESTION,
saveNodeDistMirrorAndRetry
} from './node-dist-mirror-recovery'
import type { SshManagedServerStatus } from '../../../shared/ssh-types'
import { useAppStore } from '@/store'
import {
beginSshConnect,
endSshConnect,
isSshConnectInFlight,
resetSshConnectInFlightForTests
} from './ssh-connect-in-flight'
function setManagedServer(managedServer: SshManagedServerStatus): void {
useAppStore.getState().setSshConnectionState('ssh-1', {
targetId: 'ssh-1',
status: managedServer.kind === 'managed' ? 'connected' : 'error',
error: null,
reconnectAttempt: 0,
managedServer
})
}
function deps() {
const calls: string[] = []
let finishConnect: () => void = () => {}
@@ -27,7 +40,10 @@ function deps() {
}
}
afterEach(() => resetSshConnectInFlightForTests())
afterEach(() => {
resetSshConnectInFlightForTests()
useAppStore.getState().clearRemovedSshTargetState('ssh-1')
})
describe('save a Node.js mirror and retry', () => {
it('saves the normalized mirror first, then retries the connect as an in-flight attempt', async () => {
@@ -62,12 +78,34 @@ describe('save a Node.js mirror and retry', () => {
expect(d.connect).not.toHaveBeenCalled()
})
it('saves but leaves a connect already dialing to finish on its own', async () => {
it('retries with the new mirror once a connect already dialing fails the same way', async () => {
const d = deps()
beginSshConnect('ssh-1')
const result = await saveNodeDistMirrorAndRetry('ssh-1', 'https://mirror.example/node', d)
expect(result.outcome).toBe('retrying')
expect(d.save).toHaveBeenCalled()
expect(d.calls).toEqual(['save https://mirror.example/node'])
// The dialing connect misses the mirror and fails with the same blocked download.
setManagedServer({
kind: 'relay',
reason: 'failed',
nodeDownloadBlocked: { mirror: 'unset' }
})
endSshConnect('ssh-1')
await vi.waitFor(() => expect(d.connect).toHaveBeenCalledWith('ssh-1'))
expect(d.calls).toEqual(['save https://mirror.example/node', 'connect ssh-1'])
expect(isSshConnectInFlight('ssh-1')).toBe(true)
})
it('does not reconnect when the connect already dialing succeeded', async () => {
const d = deps()
beginSshConnect('ssh-1')
const result = await saveNodeDistMirrorAndRetry('ssh-1', 'https://mirror.example/node', d)
setManagedServer({ kind: 'managed', environmentId: 'env-1' })
endSshConnect('ssh-1')
if (result.outcome === 'retrying') {
await result.retry
}
expect(d.connect).not.toHaveBeenCalled()
})
@@ -7,8 +7,16 @@ import type { SshManagedServerStatus } from '../../../shared/ssh-types'
import { translate } from '@/i18n/i18n'
import { isPairedWebClientWindow } from '@/lib/desktop-window-chrome'
import { useAppStore } from '@/store'
import { isSshConnectInFlight, trackSshConnect } from './ssh-connect-in-flight'
import { nodeMirrorRecovery, type NodeMirrorRecovery } from './ssh-host-node-download-copy'
import {
isSshConnectInFlight,
subscribeSshConnectInFlight,
trackSshConnect
} from './ssh-connect-in-flight'
import {
blockedNodeDownload,
nodeMirrorRecovery,
type NodeMirrorRecovery
} from './ssh-host-node-download-copy'
export const NODE_DIST_MIRROR_SUGGESTION = 'https://npmmirror.com/mirrors/node/'
@@ -107,9 +115,27 @@ export async function saveNodeDistMirrorAndRetry(
)
}
}
// Why: a connect already dialing keeps its own attempt; a second one would race it.
if (isSshConnectInFlight(targetId)) {
return { outcome: 'retrying', retry: Promise.resolve() }
if (!isSshConnectInFlight(targetId)) {
return { outcome: 'retrying', retry: trackSshConnect(targetId, deps.connect(targetId)) }
}
return { outcome: 'retrying', retry: trackSshConnect(targetId, deps.connect(targetId)) }
// Why wait: a second connect would race the one dialing, which may be past the download already.
// The status is current once the lock clears: main publishes a failed setup before it rejects.
const retry = sshConnectSettled(targetId).then(() =>
blockedNodeDownload(useAppStore.getState().sshConnectionStates.get(targetId)?.managedServer)
? trackSshConnect(targetId, deps.connect(targetId))
: undefined
)
return { outcome: 'retrying', retry }
}
/** Resolves once no connect for `targetId` holds the shared in-flight lock. */
function sshConnectSettled(targetId: string): Promise<void> {
return new Promise((resolve) => {
const unsubscribe = subscribeSshConnectInFlight(() => {
if (!isSshConnectInFlight(targetId)) {
unsubscribe()
resolve()
}
})
})
}