mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
fix(orchestration): keep recovery off renderer startup barrier
This commit is contained in:
+10
-7
@@ -137,6 +137,7 @@ import {
|
||||
import { maybeRedirectAppImageCliLaunch } from './startup/appimage-cli-redirect'
|
||||
import { maybeRedirectPackagedCliEntryLaunch } from './startup/packaged-cli-entry-redirect'
|
||||
import { startFirstWindowStartupServices } from './startup/first-window-startup-services'
|
||||
import { recoverLegacyWorkerTerminalsForRendererStartup } from './startup/legacy-worker-renderer-recovery'
|
||||
import { createWslCliReconciliationStartupBarrier } from './startup/wsl-cli-reconciliation-startup-barrier'
|
||||
import { getDevInstanceIdentity } from './startup/dev-instance-identity'
|
||||
import { hydrateShellPath, mergePathSegments } from './startup/hydrate-shell-path'
|
||||
@@ -728,15 +729,17 @@ if (hasSingleInstanceLock) {
|
||||
headlessBrowserDisplayAvailable = ensureVirtualDisplayForHeadlessServe({ isServeMode })
|
||||
}
|
||||
|
||||
ipcMain.handle('app:awaitFirstWindowStartupServices', async () => {
|
||||
// Why: restored WSL terminals get a bounded chance to receive launcher repairs before window rendering proceeds.
|
||||
await Promise.all([
|
||||
ipcMain.handle('app:awaitFirstWindowStartupServices', () =>
|
||||
recoverLegacyWorkerTerminalsForRendererStartup({
|
||||
firstWindowStartupServicesReady,
|
||||
managedWslCliStartupBarrierReady,
|
||||
localPtyProviderStartupReady,
|
||||
managedWslCliStartupBarrierReady
|
||||
])
|
||||
await runtime?.reconcileLegacyWorkerTerminals({ materializeRenderer: true })
|
||||
})
|
||||
reconcile: () => runtime?.reconcileLegacyWorkerTerminals({ materializeRenderer: true }),
|
||||
onDeferredRecoveryError: (error) => {
|
||||
console.warn('[orchestration] legacy worker provider-ready recovery failed', error)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Why: the renderer pulls this once its ui:openSettings listener attaches, so a Settings request queued before mount isn't lost.
|
||||
ipcMain.handle('ui:consumePendingOpenSettings', (event) =>
|
||||
|
||||
@@ -73,6 +73,11 @@ describe('startup ordering', () => {
|
||||
expect(desktopStartup).not.toContain('await managedWslCliReconciliationReady')
|
||||
expect(barrier).toContain('managedWslCliStartupBarrierReady')
|
||||
expect(barrier).not.toContain('managedWslCliReconciliationReady')
|
||||
expect(barrier).toContain('recoverLegacyWorkerTerminalsForRendererStartup({')
|
||||
expect(barrier).toContain('localPtyProviderStartupReady,')
|
||||
expect(barrier).toContain(
|
||||
'reconcile: () => runtime?.reconcileLegacyWorkerTerminals({ materializeRenderer: true })'
|
||||
)
|
||||
})
|
||||
|
||||
it('exposes managed WSL reconciliation status to headless serve clients and diagnostics', () => {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { recoverLegacyWorkerTerminalsForRendererStartup } from './legacy-worker-renderer-recovery'
|
||||
|
||||
describe('legacy worker renderer recovery', () => {
|
||||
it('hydrates after bounded barriers while provider startup remains pending', async () => {
|
||||
let resolveFirstWindow!: () => void
|
||||
let resolveWslBarrier!: () => void
|
||||
let resolveProvider!: () => void
|
||||
const firstWindowReady = new Promise<void>((resolve) => {
|
||||
resolveFirstWindow = resolve
|
||||
})
|
||||
const wslBarrierReady = new Promise<void>((resolve) => {
|
||||
resolveWslBarrier = resolve
|
||||
})
|
||||
const providerReady = new Promise<void>((resolve) => {
|
||||
resolveProvider = resolve
|
||||
})
|
||||
const reconcile = vi.fn().mockResolvedValue(undefined)
|
||||
const startup = recoverLegacyWorkerTerminalsForRendererStartup({
|
||||
firstWindowStartupServicesReady: firstWindowReady,
|
||||
managedWslCliStartupBarrierReady: wslBarrierReady,
|
||||
localPtyProviderStartupReady: providerReady,
|
||||
reconcile,
|
||||
onDeferredRecoveryError: vi.fn()
|
||||
})
|
||||
|
||||
resolveFirstWindow()
|
||||
await Promise.resolve()
|
||||
expect(reconcile).not.toHaveBeenCalled()
|
||||
|
||||
resolveWslBarrier()
|
||||
await startup
|
||||
expect(reconcile).toHaveBeenCalledTimes(1)
|
||||
|
||||
resolveProvider()
|
||||
await vi.waitFor(() => expect(reconcile).toHaveBeenCalledTimes(2))
|
||||
})
|
||||
|
||||
it('retries after initial recovery when the provider is already ready', async () => {
|
||||
const reconcile = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
await recoverLegacyWorkerTerminalsForRendererStartup({
|
||||
firstWindowStartupServicesReady: Promise.resolve(),
|
||||
managedWslCliStartupBarrierReady: Promise.resolve(),
|
||||
localPtyProviderStartupReady: Promise.resolve(),
|
||||
reconcile,
|
||||
onDeferredRecoveryError: vi.fn()
|
||||
})
|
||||
|
||||
await vi.waitFor(() => expect(reconcile).toHaveBeenCalledTimes(2))
|
||||
})
|
||||
|
||||
it('contains provider startup rejection after initial recovery', async () => {
|
||||
const providerError = new Error('provider failed')
|
||||
const reconcile = vi.fn().mockResolvedValue(undefined)
|
||||
let reportError!: (error: unknown) => void
|
||||
const reportedError = new Promise<unknown>((resolve) => {
|
||||
reportError = resolve
|
||||
})
|
||||
|
||||
await recoverLegacyWorkerTerminalsForRendererStartup({
|
||||
firstWindowStartupServicesReady: Promise.resolve(),
|
||||
managedWslCliStartupBarrierReady: Promise.resolve(),
|
||||
localPtyProviderStartupReady: Promise.reject(providerError),
|
||||
reconcile,
|
||||
onDeferredRecoveryError: reportError
|
||||
})
|
||||
|
||||
await expect(reportedError).resolves.toBe(providerError)
|
||||
expect(reconcile).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('contains deferred recovery rejection', async () => {
|
||||
const recoveryError = new Error('recovery failed')
|
||||
const reconcile = vi.fn().mockResolvedValueOnce(undefined).mockRejectedValueOnce(recoveryError)
|
||||
let reportError!: (error: unknown) => void
|
||||
const reportedError = new Promise<unknown>((resolve) => {
|
||||
reportError = resolve
|
||||
})
|
||||
|
||||
await recoverLegacyWorkerTerminalsForRendererStartup({
|
||||
firstWindowStartupServicesReady: Promise.resolve(),
|
||||
managedWslCliStartupBarrierReady: Promise.resolve(),
|
||||
localPtyProviderStartupReady: Promise.resolve(),
|
||||
reconcile,
|
||||
onDeferredRecoveryError: reportError
|
||||
})
|
||||
|
||||
await expect(reportedError).resolves.toBe(recoveryError)
|
||||
expect(reconcile).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
type LegacyWorkerRendererRecoveryOptions = {
|
||||
firstWindowStartupServicesReady: Promise<void>
|
||||
managedWslCliStartupBarrierReady: Promise<void>
|
||||
localPtyProviderStartupReady: Promise<void>
|
||||
reconcile: () => Promise<unknown> | undefined
|
||||
onDeferredRecoveryError: (error: unknown) => void
|
||||
}
|
||||
|
||||
export async function recoverLegacyWorkerTerminalsForRendererStartup(
|
||||
options: LegacyWorkerRendererRecoveryOptions
|
||||
): Promise<void> {
|
||||
const providerStartupResult = options.localPtyProviderStartupReady.then(
|
||||
() => ({ ok: true as const }),
|
||||
(error: unknown) => ({ ok: false as const, error })
|
||||
)
|
||||
await Promise.all([
|
||||
options.firstWindowStartupServicesReady,
|
||||
options.managedWslCliStartupBarrierReady
|
||||
])
|
||||
await options.reconcile()
|
||||
void providerStartupResult
|
||||
.then(async (result) => {
|
||||
if (!result.ok) {
|
||||
throw result.error
|
||||
}
|
||||
await options.reconcile()
|
||||
})
|
||||
.catch(options.onDeferredRecoveryError)
|
||||
}
|
||||
Reference in New Issue
Block a user