mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
fix(renderer): re-arm runtime event gate when reconcile throws
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -58,6 +58,45 @@ describe('buildRuntimeClientEventEnvironmentKey', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('runtime client-event subscription gate', () => {
|
||||
it('re-arms after a failed reconcile so the next publication retries', async () => {
|
||||
let failNextStatusRead = false
|
||||
const statusReads: string[] = []
|
||||
const runtimeStatusByEnvironmentId = new Map<string, { status: string } | null>()
|
||||
runtimeStatusByEnvironmentId.get = (environmentId: string) => {
|
||||
statusReads.push(environmentId)
|
||||
if (failNextStatusRead) {
|
||||
failNextStatusRead = false
|
||||
throw new Error('reconcile failed')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
const storeState = createHarnessStoreState({
|
||||
tabsByWorktree: {},
|
||||
runtimeEnvironments: [{ id: 'env-1' }],
|
||||
runtimeStatusByEnvironmentId
|
||||
})
|
||||
const harness = await loadIpcEventsHarness(storeState)
|
||||
const { useAppStore } = await import('../store')
|
||||
const { bumpRuntimeClientEventSubscriptionGeneration } =
|
||||
await import('@/runtime/runtime-client-event-subscription-invalidation')
|
||||
harness.useIpcEvents()
|
||||
const reconcile = vi.mocked(useAppStore.subscribe).mock.calls[0]?.[0] as (
|
||||
state: unknown
|
||||
) => void
|
||||
|
||||
bumpRuntimeClientEventSubscriptionGeneration()
|
||||
failNextStatusRead = true
|
||||
expect(() => reconcile(storeState)).toThrow('reconcile failed')
|
||||
const readsAfterFailure = statusReads.length
|
||||
|
||||
// Same store references as the failed publication: only the failure re-arm can drive this.
|
||||
reconcile(storeState)
|
||||
|
||||
expect(statusReads.length).toBeGreaterThan(readsAfterFailure)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNewlyConnectedRuntimeEnvironmentIds', () => {
|
||||
it('returns only environments that became connected', () => {
|
||||
expect(getNewlyConnectedRuntimeEnvironmentIds(['env-a'], ['env-a', 'env-b'])).toEqual(['env-b'])
|
||||
|
||||
@@ -183,7 +183,10 @@ import {
|
||||
type DirectSshConnectedStateOrigin
|
||||
} from './direct-ssh-state-routing'
|
||||
import { isDirectSshReconnectCoordinatorRoutingEnabled } from './direct-ssh-reconnect-rollout'
|
||||
import { createRuntimeClientEventSubscriptionInvalidationGate } from '@/runtime/runtime-client-event-subscription-invalidation'
|
||||
import {
|
||||
bumpRuntimeClientEventSubscriptionGeneration,
|
||||
createRuntimeClientEventSubscriptionInvalidationGate
|
||||
} from '@/runtime/runtime-client-event-subscription-invalidation'
|
||||
|
||||
function getShortcutPlatform(): NodeJS.Platform {
|
||||
if (navigator.userAgent.includes('Mac')) {
|
||||
@@ -1037,41 +1040,47 @@ export function useIpcEvents(): void {
|
||||
if (!runtimeClientEventSubscriptionInvalidation.changed(state)) {
|
||||
return
|
||||
}
|
||||
const nextEnvironmentIds = getRuntimeClientEventEnvironmentIds()
|
||||
const nextKey = buildRuntimeClientEventEnvironmentKey(nextEnvironmentIds)
|
||||
const nextReachableEnvironmentIds = getReachableRuntimeEnvironmentIds()
|
||||
const nextReachableKey = buildRuntimeClientEventEnvironmentKey(nextReachableEnvironmentIds)
|
||||
if (
|
||||
nextKey === runtimeClientEventEnvironmentKey &&
|
||||
nextReachableKey === reachableRuntimeEnvironmentKey
|
||||
) {
|
||||
return
|
||||
try {
|
||||
const nextEnvironmentIds = getRuntimeClientEventEnvironmentIds()
|
||||
const nextKey = buildRuntimeClientEventEnvironmentKey(nextEnvironmentIds)
|
||||
const nextReachableEnvironmentIds = getReachableRuntimeEnvironmentIds()
|
||||
const nextReachableKey = buildRuntimeClientEventEnvironmentKey(nextReachableEnvironmentIds)
|
||||
if (
|
||||
nextKey === runtimeClientEventEnvironmentKey &&
|
||||
nextReachableKey === reachableRuntimeEnvironmentKey
|
||||
) {
|
||||
return
|
||||
}
|
||||
for (const environmentId of getRuntimeProjectRefreshEnvironmentIds({
|
||||
previousDesired: runtimeClientEventEnvironmentIds,
|
||||
nextDesired: nextEnvironmentIds,
|
||||
previousReachable: reachableRuntimeEnvironmentIds,
|
||||
nextReachable: nextReachableEnvironmentIds
|
||||
})) {
|
||||
runtimeProjectRefreshScheduler.request(environmentId)
|
||||
}
|
||||
for (const environmentId of getNewlyDisconnectedRuntimeEnvironmentIds(
|
||||
reachableRuntimeEnvironmentIds,
|
||||
nextReachableEnvironmentIds
|
||||
)) {
|
||||
// No-op when the environment has no SSH bucket (e.g. web client).
|
||||
useAppStore.getState().markEnvironmentSshStateStale(environmentId)
|
||||
}
|
||||
runtimeClientEventEnvironmentIds = nextEnvironmentIds
|
||||
reachableRuntimeEnvironmentIds = nextReachableEnvironmentIds
|
||||
// Nested stale-state writes can advance the out-of-store SSH generation.
|
||||
runtimeClientEventEnvironmentKey = buildRuntimeClientEventEnvironmentKey(
|
||||
runtimeClientEventEnvironmentIds
|
||||
)
|
||||
reachableRuntimeEnvironmentKey = buildRuntimeClientEventEnvironmentKey(
|
||||
reachableRuntimeEnvironmentIds
|
||||
)
|
||||
runtimeClientEventsSync.sync()
|
||||
} catch (error) {
|
||||
// Why: the gate already consumed this publication, so re-arm it or the subscription stays stale forever.
|
||||
bumpRuntimeClientEventSubscriptionGeneration()
|
||||
throw error
|
||||
}
|
||||
for (const environmentId of getRuntimeProjectRefreshEnvironmentIds({
|
||||
previousDesired: runtimeClientEventEnvironmentIds,
|
||||
nextDesired: nextEnvironmentIds,
|
||||
previousReachable: reachableRuntimeEnvironmentIds,
|
||||
nextReachable: nextReachableEnvironmentIds
|
||||
})) {
|
||||
runtimeProjectRefreshScheduler.request(environmentId)
|
||||
}
|
||||
for (const environmentId of getNewlyDisconnectedRuntimeEnvironmentIds(
|
||||
reachableRuntimeEnvironmentIds,
|
||||
nextReachableEnvironmentIds
|
||||
)) {
|
||||
// No-op when the environment has no SSH bucket (e.g. web client).
|
||||
useAppStore.getState().markEnvironmentSshStateStale(environmentId)
|
||||
}
|
||||
runtimeClientEventEnvironmentIds = nextEnvironmentIds
|
||||
reachableRuntimeEnvironmentIds = nextReachableEnvironmentIds
|
||||
// Nested stale-state writes can advance the out-of-store SSH generation.
|
||||
runtimeClientEventEnvironmentKey = buildRuntimeClientEventEnvironmentKey(
|
||||
runtimeClientEventEnvironmentIds
|
||||
)
|
||||
reachableRuntimeEnvironmentKey = buildRuntimeClientEventEnvironmentKey(
|
||||
reachableRuntimeEnvironmentIds
|
||||
)
|
||||
runtimeClientEventsSync.sync()
|
||||
})
|
||||
unsubs.push(runtimeClientEventsSync.stop)
|
||||
unsubs.push(runtimeProjectRefreshScheduler.stop)
|
||||
|
||||
Reference in New Issue
Block a user