From 1def7bb217bd5ded4724e2f2315654c87905f349 Mon Sep 17 00:00:00 2001 From: Daniyal <115576848+daniyal-sh@users.noreply.github.com> Date: Wed, 14 Oct 2026 02:57:03 -0500 Subject: [PATCH 1/3] fix(desktop): stop blocking the renderer during topology polling --- apps/desktop/src/ipc/DesktopIpcHandlers.ts | 2 + apps/desktop/src/ipc/methods/window.ts | 137 ++++++++++--------- apps/desktop/src/preload.ts | 26 ++-- apps/desktop/src/preloadSnapshot.test.ts | 62 +++++++++ apps/desktop/src/preloadSnapshot.ts | 24 ++++ apps/web/src/connection/desktopLocal.test.ts | 41 ++++++ apps/web/src/connection/desktopLocal.ts | 64 +++++---- apps/web/src/connection/platform.ts | 2 +- docs/internals/performance-regressions.md | 5 +- packages/contracts/src/ipc.ts | 2 + 10 files changed, 268 insertions(+), 97 deletions(-) create mode 100644 apps/desktop/src/preloadSnapshot.test.ts create mode 100644 apps/desktop/src/preloadSnapshot.ts diff --git a/apps/desktop/src/ipc/DesktopIpcHandlers.ts b/apps/desktop/src/ipc/DesktopIpcHandlers.ts index caed223e90e8..80058008e46c 100644 --- a/apps/desktop/src/ipc/DesktopIpcHandlers.ts +++ b/apps/desktop/src/ipc/DesktopIpcHandlers.ts @@ -40,6 +40,7 @@ import { import { getAppBranding, getLocalEnvironmentBootstraps, + readLocalEnvironmentBootstraps, getLocalEnvironmentBearerToken, getSystemLocale, getWindowFullscreenState, @@ -97,6 +98,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers" yield* ipc.handleSync(getSystemLocale); yield* ipc.handleSync(getWindowFullscreenState); yield* ipc.handleSync(getLocalEnvironmentBootstraps); + yield* ipc.handle(readLocalEnvironmentBootstraps); yield* ipc.handleSync(getLocalEnvironmentEnabled); yield* ipc.handle(setLocalEnvironmentEnabled); yield* ipc.handle(getLocalEnvironmentBearerToken); diff --git a/apps/desktop/src/ipc/methods/window.ts b/apps/desktop/src/ipc/methods/window.ts index 33895ffffbd1..c19e1c6c483d 100644 --- a/apps/desktop/src/ipc/methods/window.ts +++ b/apps/desktop/src/ipc/methods/window.ts @@ -90,73 +90,84 @@ export const getWindowFullscreenState = DesktopIpc.makeSyncIpcMethod({ }), }); -export const getLocalEnvironmentBootstraps = DesktopIpc.makeSyncIpcMethod({ - channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL, - result: Schema.Array(DesktopEnvironmentBootstrapSchema), - handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBootstraps")(function* () { - const pool = yield* DesktopBackendPool.DesktopBackendPool; - const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration; - const instances = yield* pool.list; - const bootstraps: DesktopEnvironmentBootstrap[] = []; - for (const instance of instances) { - const isPrimary = instance.id === PRIMARY_LOCAL_ENVIRONMENT_ID; - const config = yield* instance.currentConfig; - const snapshot = yield* instance.snapshot; - // A secondary backend (e.g. a parallel WSL backend) that hasn't produced - // a config yet (mid-registration, before its first start cycle) or that - // is retrying a *transient* preflight failure (WSL VM still booting, a - // not-yet-built linux server entry) is not listening on a port. We - // surface it as a *pending* bootstrap (null endpoints, no token) so the - // renderer can show a "Connecting…" indicator while it retries — null - // endpoints keep the renderer from dialing the dead port, avoiding the - // needless /api/auth/bootstrap/bearer error cycles a real endpoint would - // trigger. - if (Option.isNone(config) || Option.isSome(config.value.preflightFailure)) { - // Skip the primary (same-origin, no "connecting" affordance) and skip a - // secondary whose preflight failed *fatally* (no node, wrong version, - // missing build tools): it has stopped retrying, so an indefinite - // "Connecting…" would be misleading — its error is surfaced by the - // WSL-state UI instead. - const fatalPreflight = - Option.isSome(config) && - Option.isSome(config.value.preflightFailure) && - config.value.preflightFailure.value.fatal; - const stoppedPreflight = - Option.isSome(config) && - Option.isSome(config.value.preflightFailure) && - (!snapshot.desiredRunning || !snapshot.restartScheduled); - if (isPrimary || fatalPreflight || stoppedPreflight) continue; - bootstraps.push({ - id: instance.id, - label: yield* instance.label, - runningDistro: null, - httpBaseUrl: null, - wsBaseUrl: null, - }); - continue; - } - const { bootstrap, httpBaseUrl } = config.value; - const runningDistro = config.value.runningDistro ?? null; +const readLocalEnvironmentBootstrapSnapshot = Effect.fn( + "desktop.ipc.window.getLocalEnvironmentBootstraps", +)(function* () { + const pool = yield* DesktopBackendPool.DesktopBackendPool; + const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration; + const instances = yield* pool.list; + const bootstraps: DesktopEnvironmentBootstrap[] = []; + for (const instance of instances) { + const isPrimary = instance.id === PRIMARY_LOCAL_ENVIRONMENT_ID; + const config = yield* instance.currentConfig; + const snapshot = yield* instance.snapshot; + // A secondary backend (e.g. a parallel WSL backend) that hasn't produced + // a config yet (mid-registration, before its first start cycle) or that + // is retrying a *transient* preflight failure (WSL VM still booting, a + // not-yet-built linux server entry) is not listening on a port. We + // surface it as a *pending* bootstrap (null endpoints, no token) so the + // renderer can show a "Connecting…" indicator while it retries — null + // endpoints keep the renderer from dialing the dead port, avoiding the + // needless /api/auth/bootstrap/bearer error cycles a real endpoint would + // trigger. + if (Option.isNone(config) || Option.isSome(config.value.preflightFailure)) { + // Skip the primary (same-origin, no "connecting" affordance) and skip a + // secondary whose preflight failed *fatally* (no node, wrong version, + // missing build tools): it has stopped retrying, so an indefinite + // "Connecting…" would be misleading — its error is surfaced by the + // WSL-state UI instead. + const fatalPreflight = + Option.isSome(config) && + Option.isSome(config.value.preflightFailure) && + config.value.preflightFailure.value.fatal; + const stoppedPreflight = + Option.isSome(config) && + Option.isSome(config.value.preflightFailure) && + (!snapshot.desiredRunning || !snapshot.restartScheduled); + if (isPrimary || fatalPreflight || stoppedPreflight) continue; bootstraps.push({ id: instance.id, - label: runningDistro === null ? yield* instance.label : `WSL (${runningDistro})`, - runningDistro, - httpBaseUrl: httpBaseUrl.href, - wsBaseUrl: toWebSocketBaseUrl(httpBaseUrl), - // A backend launched with the desktop secret accepts whichever token - // the secret derives for the current window, so hand out that one - // rather than the token frozen into its launch config. Every backend - // the desktop launches (primary, staged or mounted WSL runtime) is the - // server build bundled with this desktop, so it understands the secret. - ...(bootstrap.desktopBootstrapSecret - ? { bootstrapToken: yield* configuration.currentBootstrapToken } - : bootstrap.desktopBootstrapToken - ? { bootstrapToken: bootstrap.desktopBootstrapToken } - : {}), + label: yield* instance.label, + runningDistro: null, + httpBaseUrl: null, + wsBaseUrl: null, }); + continue; } - return bootstraps; - }), + const { bootstrap, httpBaseUrl } = config.value; + const runningDistro = config.value.runningDistro ?? null; + bootstraps.push({ + id: instance.id, + label: runningDistro === null ? yield* instance.label : `WSL (${runningDistro})`, + runningDistro, + httpBaseUrl: httpBaseUrl.href, + wsBaseUrl: toWebSocketBaseUrl(httpBaseUrl), + // A backend launched with the desktop secret accepts whichever token + // the secret derives for the current window, so hand out that one + // rather than the token frozen into its launch config. Every backend + // the desktop launches (primary, staged or mounted WSL runtime) is the + // server build bundled with this desktop, so it understands the secret. + ...(bootstrap.desktopBootstrapSecret + ? { bootstrapToken: yield* configuration.currentBootstrapToken } + : bootstrap.desktopBootstrapToken + ? { bootstrapToken: bootstrap.desktopBootstrapToken } + : {}), + }); + } + return bootstraps; +}); + +export const getLocalEnvironmentBootstraps = DesktopIpc.makeSyncIpcMethod({ + channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL, + result: Schema.Array(DesktopEnvironmentBootstrapSchema), + handler: readLocalEnvironmentBootstrapSnapshot, +}); + +export const readLocalEnvironmentBootstraps = DesktopIpc.makeIpcMethod({ + channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL, + payload: Schema.Void, + result: Schema.Array(DesktopEnvironmentBootstrapSchema), + handler: readLocalEnvironmentBootstrapSnapshot, }); // Pull the distro selection out of a backend instance id like diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index efd49ffa3864..6ddcf29b4e04 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -12,6 +12,7 @@ import { contextBridge, ipcRenderer, webFrame, webUtils } from "electron"; import * as IpcChannels from "./ipc/channels.ts"; import { mergeLegacyLocalStorage } from "./legacyLocalStorageMerge.ts"; +import { createPreloadSnapshot } from "./preloadSnapshot.ts"; const SNAP_SHOT_EVENT_TYPES = new Set([ "requested", @@ -76,6 +77,19 @@ function unwrapEnsureSshEnvironmentResult(result: unknown) { return result as Awaited>; } +const readBootstraps = ( + result: unknown, +): ReturnType => + Array.isArray(result) ? result : []; +const localEnvironmentBootstraps = createPreloadSnapshot( + () => readBootstraps(ipcRenderer.sendSync(IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL)), + async () => + readBootstraps(await ipcRenderer.invoke(IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL)), +); +// This setting changes only through a relaunch; repeated renderer reads do +// not need to block on the main process. +let localEnvironmentEnabled: boolean | undefined; + contextBridge.exposeInMainWorld("desktopBridge", { getAppBranding: () => { const result = ipcRenderer.sendSync(IpcChannels.GET_APP_BRANDING_CHANNEL); @@ -102,17 +116,13 @@ contextBridge.exposeInMainWorld("desktopBridge", { const result = ipcRenderer.sendSync(IpcChannels.GET_SYSTEM_LOCALE_CHANNEL); return typeof result === "string" ? result : null; }, - getLocalEnvironmentBootstraps: () => { - const result = ipcRenderer.sendSync(IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL); - if (!Array.isArray(result)) { - return []; - } - return result as ReturnType; - }, + getLocalEnvironmentBootstraps: localEnvironmentBootstraps.read, + readLocalEnvironmentBootstraps: localEnvironmentBootstraps.refresh, getLocalEnvironmentBearerToken: () => ipcRenderer.invoke(IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL), getLocalEnvironmentEnabled: () => - ipcRenderer.sendSync(IpcChannels.GET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL) !== false, + (localEnvironmentEnabled ??= + ipcRenderer.sendSync(IpcChannels.GET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL) !== false), setLocalEnvironmentEnabled: (enabled) => ipcRenderer.invoke(IpcChannels.SET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL, enabled), getClientSettings: () => ipcRenderer.invoke(IpcChannels.GET_CLIENT_SETTINGS_CHANNEL), diff --git a/apps/desktop/src/preloadSnapshot.test.ts b/apps/desktop/src/preloadSnapshot.test.ts new file mode 100644 index 000000000000..fe3c1963a999 --- /dev/null +++ b/apps/desktop/src/preloadSnapshot.test.ts @@ -0,0 +1,62 @@ +import { expect, it } from "vite-plus/test"; + +import { createPreloadSnapshot } from "./preloadSnapshot.ts"; + +it("keeps reads responsive while the main process is busy and shares a refresh", async () => { + let synchronousReads = 0; + let asynchronousReads = 0; + let complete!: (value: string[]) => void; + const latest = new Promise((resolve) => { + complete = resolve; + }); + const snapshot = createPreloadSnapshot( + () => { + synchronousReads++; + return ["primary"]; + }, + () => { + asynchronousReads++; + return latest; + }, + ); + + expect(snapshot.read()).toEqual(["primary"]); + const refresh = snapshot.refresh(); + expect(snapshot.refresh()).toBe(refresh); + for (let read = 0; read < 1_000; read++) expect(snapshot.read()).toEqual(["primary"]); + expect(synchronousReads).toBe(1); + expect(asynchronousReads).toBe(1); + + complete(["primary", "wsl"]); + await refresh; + expect(snapshot.read()).toEqual(["primary", "wsl"]); + expect(synchronousReads).toBe(1); +}); + +it("preserves the last successful topology after IPC fails and recovers on the next refresh", async () => { + let fail = true; + const snapshot = createPreloadSnapshot( + () => ["primary", "wsl"], + async () => { + if (fail) throw new Error("IPC unavailable"); + return []; + }, + ); + expect(snapshot.read()).toEqual(["primary", "wsl"]); + await expect(snapshot.refresh()).rejects.toThrow("IPC unavailable"); + expect(snapshot.read()).toEqual(["primary", "wsl"]); + fail = false; + await snapshot.refresh(); + expect(snapshot.read()).toEqual([]); +}); + +it("uses an asynchronous startup result without a synchronous IPC read", async () => { + const snapshot = createPreloadSnapshot( + () => { + throw new Error("unexpected synchronous IPC"); + }, + async () => ["wsl"], + ); + await snapshot.refresh(); + expect(snapshot.read()).toEqual(["wsl"]); +}); diff --git a/apps/desktop/src/preloadSnapshot.ts b/apps/desktop/src/preloadSnapshot.ts new file mode 100644 index 000000000000..dcb446b43751 --- /dev/null +++ b/apps/desktop/src/preloadSnapshot.ts @@ -0,0 +1,24 @@ +/** Keep synchronous startup consumers while refreshing through nonblocking IPC. */ +export function createPreloadSnapshot(readInitial: () => T, readLatest: () => Promise) { + let snapshot: { readonly value: T } | undefined; + let pending: Promise | undefined; + + return { + read: (): T => { + snapshot ??= { value: readInitial() }; + return snapshot.value; + }, + refresh: (): Promise => { + if (pending !== undefined) return pending; + pending = readLatest() + .then((value) => { + snapshot = { value }; + return value; + }) + .finally(() => { + pending = undefined; + }); + return pending; + }, + }; +} diff --git a/apps/web/src/connection/desktopLocal.test.ts b/apps/web/src/connection/desktopLocal.test.ts index 7a31aa182099..f5f5fe578ad1 100644 --- a/apps/web/src/connection/desktopLocal.test.ts +++ b/apps/web/src/connection/desktopLocal.test.ts @@ -55,6 +55,47 @@ describe("desktop local connection identity", () => { }); describe("desktop local topology reads", () => { + it("refreshes asynchronously without calling the synchronous bridge and preserves failures", async () => { + const secondary: DesktopEnvironmentBootstrap = { + id: "wsl:default", + label: "WSL", + runningDistro: "Ubuntu", + httpBaseUrl: "http://127.0.0.1:4000", + wsBaseUrl: "ws://127.0.0.1:4000", + bootstrapToken: "bootstrap-1", + }; + let entries = [secondary]; + let failure: Error | undefined; + const reader = createDesktopSecondaryBootstrapsReader(() => ({ + getLocalEnvironmentBootstraps: () => { + throw new Error("synchronous IPC unavailable"); + }, + readLocalEnvironmentBootstraps: async () => { + if (failure !== undefined) throw failure; + return entries.map((entry) => ({ ...entry })); + }, + })); + const connected = await reader.refresh(); + expect(connected).toEqual({ _tag: "Success", bootstraps: [secondary] }); + if (connected._tag !== "Success") throw new Error("expected connected topology"); + expect(reader.readSnapshot()).toBe(connected.bootstraps); + expect(await reader.refresh()).toEqual(connected); + failure = new Error("main process unavailable"); + expect(await reader.refresh()).toEqual({ _tag: "Failure", cause: failure }); + expect(reader.readSnapshot()).toBe(connected.bootstraps); + failure = undefined; + entries = []; + expect(await reader.refresh()).toEqual({ _tag: "Success", bootstraps: [] }); + expect(reader.readSnapshot()).toEqual([]); + }); + + it("refreshes through an older desktop's synchronous bridge", async () => { + const reader = createDesktopSecondaryBootstrapsReader(() => ({ + getLocalEnvironmentBootstraps: () => [], + })); + expect(await reader.refresh()).toEqual({ _tag: "Success", bootstraps: [] }); + }); + it("reuses snapshots when polling returns fresh objects for the same topology", () => { const secondary: DesktopEnvironmentBootstrap = { id: "wsl:default", diff --git a/apps/web/src/connection/desktopLocal.ts b/apps/web/src/connection/desktopLocal.ts index 515146d73ed6..2a918856bb9c 100644 --- a/apps/web/src/connection/desktopLocal.ts +++ b/apps/web/src/connection/desktopLocal.ts @@ -54,6 +54,7 @@ export type DesktopSecondaryBootstrapsRead = export interface DesktopSecondaryBootstrapsReader { readonly readResult: () => DesktopSecondaryBootstrapsRead; + readonly refresh: () => Promise; readonly readSnapshot: () => ReadonlyArray; } @@ -64,10 +65,36 @@ export interface DesktopSecondaryBootstrapsReader { * platform's retained registrations. */ export function createDesktopSecondaryBootstrapsReader( - resolveBridge: () => Pick | undefined, + resolveBridge: () => + | Pick + | undefined, ): DesktopSecondaryBootstrapsReader { let snapshot: ReadonlyArray = []; + const updateSnapshot = ( + entries: ReadonlyArray, + ): DesktopSecondaryBootstrapsRead => { + const next = entries.filter((entry) => entry.id !== PRIMARY_LOCAL_ENVIRONMENT_ID); + if ( + next.length !== snapshot.length || + next.some((entry, index) => { + const previous = snapshot[index]; + return ( + previous === undefined || + entry.id !== previous.id || + entry.label !== previous.label || + entry.runningDistro !== previous.runningDistro || + entry.httpBaseUrl !== previous.httpBaseUrl || + entry.wsBaseUrl !== previous.wsBaseUrl || + entry.bootstrapToken !== previous.bootstrapToken + ); + }) + ) { + snapshot = next; + } + return { _tag: "Success", bootstraps: snapshot }; + }; + const readResult = (): DesktopSecondaryBootstrapsRead => { const bridge = resolveBridge(); if (bridge === undefined) { @@ -75,27 +102,7 @@ export function createDesktopSecondaryBootstrapsReader( return { _tag: "Success", bootstraps: snapshot }; } try { - const next = bridge - .getLocalEnvironmentBootstraps() - .filter((entry) => entry.id !== PRIMARY_LOCAL_ENVIRONMENT_ID); - if ( - next.length !== snapshot.length || - next.some((entry, index) => { - const previous = snapshot[index]; - return ( - previous === undefined || - entry.id !== previous.id || - entry.label !== previous.label || - entry.runningDistro !== previous.runningDistro || - entry.httpBaseUrl !== previous.httpBaseUrl || - entry.wsBaseUrl !== previous.wsBaseUrl || - entry.bootstrapToken !== previous.bootstrapToken - ); - }) - ) { - snapshot = next; - } - return { _tag: "Success", bootstraps: snapshot }; + return updateSnapshot(bridge.getLocalEnvironmentBootstraps()); } catch (cause) { return { _tag: "Failure", cause }; } @@ -103,6 +110,15 @@ export function createDesktopSecondaryBootstrapsReader( return { readResult, + refresh: async () => { + const bridge = resolveBridge(); + if (bridge?.readLocalEnvironmentBootstraps === undefined) return readResult(); + try { + return updateSnapshot(await bridge.readLocalEnvironmentBootstraps()); + } catch (cause) { + return { _tag: "Failure", cause }; + } + }, readSnapshot: () => { const result = readResult(); return result._tag === "Success" ? result.bootstraps : snapshot; @@ -115,8 +131,8 @@ const desktopSecondaryBootstrapsReader = createDesktopSecondaryBootstrapsReader( ); /** Read the topology while preserving failures for platform cache policy. */ -export function readDesktopSecondaryBootstrapsResult(): DesktopSecondaryBootstrapsRead { - return desktopSecondaryBootstrapsReader.readResult(); +export function readDesktopSecondaryBootstrapsResult(): Promise { + return desktopSecondaryBootstrapsReader.refresh(); } /** Read the latest successful topology snapshot for renderer consumers. */ diff --git a/apps/web/src/connection/platform.ts b/apps/web/src/connection/platform.ts index 63c6563aea70..3c57abbb92ef 100644 --- a/apps/web/src/connection/platform.ts +++ b/apps/web/src/connection/platform.ts @@ -624,7 +624,7 @@ const layerPlatformConnectionSource = Layer.effect( } } - const topologyRead = readDesktopSecondaryBootstrapsResult(); + const topologyRead = yield* Effect.promise(readDesktopSecondaryBootstrapsResult); for (const [id, cached] of secondaryRegistrationsToRetainAfterTopologyRead( previous, topologyRead, diff --git a/docs/internals/performance-regressions.md b/docs/internals/performance-regressions.md index 9440d2ab6144..896c0e6d50d6 100644 --- a/docs/internals/performance-regressions.md +++ b/docs/internals/performance-regressions.md @@ -88,7 +88,10 @@ viewport, including a small overscan region. Cached badges remain available whil released. Local Git status reads skip divergence calculations; callers that need ahead/behind counts keep the full status path. Cached fetch failures share the existing backoff and log only the actual failed attempt. Unchanged desktop environment bootstrap reads retain their array -identity so polling does not invalidate subscribers. +identity so polling does not invalidate subscribers. The desktop preload keeps the synchronous +startup snapshot, while topology polling refreshes it through asynchronous IPC. Recurring +`sendSync` calls block the renderer behind unrelated main-process work, even when the returned +snapshot is unchanged. Relay awareness queues one pending publication per thread and retains a follow-up when the thread changes during a send. Transcript deltas and tool output do not enqueue awareness work. Run, diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index e521c4c37271..cf60ce125fcf 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -1135,6 +1135,8 @@ export interface DesktopBridge { // info (omits instances whose backend hasn't produced a config yet). // The primary backend is identified by id === PRIMARY_LOCAL_ENVIRONMENT_ID. getLocalEnvironmentBootstraps: () => readonly DesktopEnvironmentBootstrap[]; + /** Refresh the preload snapshot without blocking the renderer on synchronous IPC. */ + readLocalEnvironmentBootstraps?: () => Promise; getLocalEnvironmentEnabled?: () => boolean; setLocalEnvironmentEnabled?: (enabled: boolean) => Promise; getLocalEnvironmentBearerToken: () => Promise; From 7bb786b4d2ae02e78d7361fbb8c24cd31e0e6603 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 13:33:30 +0000 Subject: [PATCH 2/3] test(desktop): trim async topology regression coverage --- apps/desktop/src/preloadSnapshot.test.ts | 87 ++++++++------------ apps/web/src/connection/desktopLocal.test.ts | 59 +++++++------ 2 files changed, 69 insertions(+), 77 deletions(-) diff --git a/apps/desktop/src/preloadSnapshot.test.ts b/apps/desktop/src/preloadSnapshot.test.ts index fe3c1963a999..05783e830032 100644 --- a/apps/desktop/src/preloadSnapshot.test.ts +++ b/apps/desktop/src/preloadSnapshot.test.ts @@ -1,62 +1,45 @@ -import { expect, it } from "vite-plus/test"; +import { expect, it, vi } from "vite-plus/test"; import { createPreloadSnapshot } from "./preloadSnapshot.ts"; -it("keeps reads responsive while the main process is busy and shares a refresh", async () => { - let synchronousReads = 0; - let asynchronousReads = 0; - let complete!: (value: string[]) => void; - const latest = new Promise((resolve) => { - complete = resolve; - }); - const snapshot = createPreloadSnapshot( - () => { - synchronousReads++; - return ["primary"]; - }, - () => { - asynchronousReads++; - return latest; - }, - ); +it("serves the startup snapshot while a shared asynchronous refresh is pending", async () => { + const initial = ["primary"]; + const latest = Promise.withResolvers(); + const readInitial = vi.fn(() => initial); + const readLatest = vi.fn(() => latest.promise); + const snapshot = createPreloadSnapshot(readInitial, readLatest); - expect(snapshot.read()).toEqual(["primary"]); - const refresh = snapshot.refresh(); - expect(snapshot.refresh()).toBe(refresh); - for (let read = 0; read < 1_000; read++) expect(snapshot.read()).toEqual(["primary"]); - expect(synchronousReads).toBe(1); - expect(asynchronousReads).toBe(1); + expect(snapshot.read()).toBe(initial); + const first = snapshot.refresh(); + const second = snapshot.refresh(); + expect(snapshot.read()).toBe(initial); + expect(snapshot.read()).toBe(initial); + expect(readInitial).toHaveBeenCalledTimes(1); + expect(readLatest).toHaveBeenCalledTimes(1); - complete(["primary", "wsl"]); - await refresh; - expect(snapshot.read()).toEqual(["primary", "wsl"]); - expect(synchronousReads).toBe(1); + const connected = ["primary", "wsl"]; + latest.resolve(connected); + expect(await first).toBe(connected); + expect(await second).toBe(connected); + expect(snapshot.read()).toBe(connected); + expect(readInitial).toHaveBeenCalledTimes(1); }); -it("preserves the last successful topology after IPC fails and recovers on the next refresh", async () => { - let fail = true; - const snapshot = createPreloadSnapshot( - () => ["primary", "wsl"], - async () => { - if (fail) throw new Error("IPC unavailable"); - return []; - }, - ); - expect(snapshot.read()).toEqual(["primary", "wsl"]); - await expect(snapshot.refresh()).rejects.toThrow("IPC unavailable"); - expect(snapshot.read()).toEqual(["primary", "wsl"]); - fail = false; - await snapshot.refresh(); - expect(snapshot.read()).toEqual([]); -}); +it("retains an async snapshot through failure and replaces it on successful recovery", async () => { + const connected = ["primary", "wsl"]; + const readInitial = vi.fn(() => ["stale"]); + const readLatest = vi + .fn<() => Promise>() + .mockResolvedValueOnce(connected) + .mockRejectedValueOnce(new Error("IPC unavailable")) + .mockResolvedValueOnce([]); + const snapshot = createPreloadSnapshot(readInitial, readLatest); -it("uses an asynchronous startup result without a synchronous IPC read", async () => { - const snapshot = createPreloadSnapshot( - () => { - throw new Error("unexpected synchronous IPC"); - }, - async () => ["wsl"], - ); await snapshot.refresh(); - expect(snapshot.read()).toEqual(["wsl"]); + expect(snapshot.read()).toBe(connected); + await expect(snapshot.refresh()).rejects.toThrow("IPC unavailable"); + expect(snapshot.read()).toBe(connected); + await expect(snapshot.refresh()).resolves.toEqual([]); + expect(snapshot.read()).toEqual([]); + expect(readInitial).not.toHaveBeenCalled(); }); diff --git a/apps/web/src/connection/desktopLocal.test.ts b/apps/web/src/connection/desktopLocal.test.ts index f5f5fe578ad1..e8a5496ad8aa 100644 --- a/apps/web/src/connection/desktopLocal.test.ts +++ b/apps/web/src/connection/desktopLocal.test.ts @@ -7,7 +7,7 @@ import { EnvironmentId, PRIMARY_LOCAL_ENVIRONMENT_ID, } from "@t3tools/contracts"; -import { describe, expect, it } from "vite-plus/test"; +import { describe, expect, it, vi } from "vite-plus/test"; import { createDesktopSecondaryBootstrapsReader, @@ -55,7 +55,7 @@ describe("desktop local connection identity", () => { }); describe("desktop local topology reads", () => { - it("refreshes asynchronously without calling the synchronous bridge and preserves failures", async () => { + it("refreshes topology asynchronously, retaining identity through unchanged reads and failures", async () => { const secondary: DesktopEnvironmentBootstrap = { id: "wsl:default", label: "WSL", @@ -64,39 +64,43 @@ describe("desktop local topology reads", () => { wsBaseUrl: "ws://127.0.0.1:4000", bootstrapToken: "bootstrap-1", }; - let entries = [secondary]; - let failure: Error | undefined; + let entries = [secondary, { ...secondary, id: PRIMARY_LOCAL_ENVIRONMENT_ID }]; + let cached = entries; + const getLocalEnvironmentBootstraps = vi.fn(() => cached); + const readLocalEnvironmentBootstraps = vi.fn(async () => { + cached = entries.map((entry) => ({ ...entry })); + return cached; + }); const reader = createDesktopSecondaryBootstrapsReader(() => ({ - getLocalEnvironmentBootstraps: () => { - throw new Error("synchronous IPC unavailable"); - }, - readLocalEnvironmentBootstraps: async () => { - if (failure !== undefined) throw failure; - return entries.map((entry) => ({ ...entry })); - }, + getLocalEnvironmentBootstraps, + readLocalEnvironmentBootstraps, })); + const connected = await reader.refresh(); expect(connected).toEqual({ _tag: "Success", bootstraps: [secondary] }); + expect(getLocalEnvironmentBootstraps).not.toHaveBeenCalled(); if (connected._tag !== "Success") throw new Error("expected connected topology"); + + const unchanged = await reader.refresh(); + if (unchanged._tag !== "Success") throw new Error("expected unchanged topology"); + expect(unchanged.bootstraps).toBe(connected.bootstraps); + expect(getLocalEnvironmentBootstraps).not.toHaveBeenCalled(); expect(reader.readSnapshot()).toBe(connected.bootstraps); - expect(await reader.refresh()).toEqual(connected); - failure = new Error("main process unavailable"); - expect(await reader.refresh()).toEqual({ _tag: "Failure", cause: failure }); + + const cause = new Error("IPC unavailable"); + readLocalEnvironmentBootstraps.mockRejectedValueOnce(cause); + expect(await reader.refresh()).toEqual({ _tag: "Failure", cause }); expect(reader.readSnapshot()).toBe(connected.bootstraps); - failure = undefined; - entries = []; - expect(await reader.refresh()).toEqual({ _tag: "Success", bootstraps: [] }); - expect(reader.readSnapshot()).toEqual([]); - }); - it("refreshes through an older desktop's synchronous bridge", async () => { - const reader = createDesktopSecondaryBootstrapsReader(() => ({ - getLocalEnvironmentBootstraps: () => [], - })); - expect(await reader.refresh()).toEqual({ _tag: "Success", bootstraps: [] }); + entries = []; + const removed = await reader.refresh(); + expect(removed).toEqual({ _tag: "Success", bootstraps: [] }); + if (removed._tag !== "Success") throw new Error("expected empty topology"); + expect(reader.readSnapshot()).toBe(removed.bootstraps); + expect(removed.bootstraps).not.toBe(connected.bootstraps); }); - it("reuses snapshots when polling returns fresh objects for the same topology", () => { + it("reuses snapshots when polling returns fresh objects for the same topology", async () => { const secondary: DesktopEnvironmentBootstrap = { id: "wsl:default", label: "WSL", @@ -111,11 +115,16 @@ describe("desktop local topology reads", () => { })); const connected = reader.readSnapshot(); + const refreshed = await reader.refresh(); + expect(refreshed).toEqual({ _tag: "Success", bootstraps: [secondary] }); + if (refreshed._tag !== "Success") throw new Error("expected legacy topology"); + expect(refreshed.bootstraps).toBe(connected); expect(reader.readSnapshot()).toBe(connected); expect(reader.readResult()).toEqual({ _tag: "Success", bootstraps: connected }); expect(reader.readSnapshot()).toBe(connected); entries = []; + expect(await reader.refresh()).toEqual({ _tag: "Success", bootstraps: [] }); const empty = reader.readSnapshot(); expect(empty).toEqual([]); expect(reader.readSnapshot()).toBe(empty); From 8795edca4036653db6c71d9e34739ee4d055fda7 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 13:40:30 +0000 Subject: [PATCH 3/3] refactor(desktop): simplify topology snapshot declarations --- apps/desktop/src/preload.ts | 3 +-- apps/desktop/src/preloadSnapshot.ts | 5 ++--- apps/web/src/connection/desktopLocal.ts | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 58a056e68fc1..9825662efc0d 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -84,8 +84,7 @@ const localEnvironmentBootstraps = createPreloadSnapshot( async () => readBootstraps(await ipcRenderer.invoke(IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL)), ); -// This setting changes only through a relaunch; repeated renderer reads do -// not need to block on the main process. +// Changes take effect on relaunch, so renderer reads can reuse the startup value. let localEnvironmentEnabled: boolean | undefined; contextBridge.exposeInMainWorld("desktopBridge", { diff --git a/apps/desktop/src/preloadSnapshot.ts b/apps/desktop/src/preloadSnapshot.ts index dcb446b43751..4fe34b2719f6 100644 --- a/apps/desktop/src/preloadSnapshot.ts +++ b/apps/desktop/src/preloadSnapshot.ts @@ -1,14 +1,13 @@ -/** Keep synchronous startup consumers while refreshing through nonblocking IPC. */ export function createPreloadSnapshot(readInitial: () => T, readLatest: () => Promise) { let snapshot: { readonly value: T } | undefined; let pending: Promise | undefined; return { - read: (): T => { + read: () => { snapshot ??= { value: readInitial() }; return snapshot.value; }, - refresh: (): Promise => { + refresh: () => { if (pending !== undefined) return pending; pending = readLatest() .then((value) => { diff --git a/apps/web/src/connection/desktopLocal.ts b/apps/web/src/connection/desktopLocal.ts index ecf2d95f7a51..85abb94b9bd5 100644 --- a/apps/web/src/connection/desktopLocal.ts +++ b/apps/web/src/connection/desktopLocal.ts @@ -121,7 +121,6 @@ const desktopSecondaryBootstrapsReader = createDesktopSecondaryBootstrapsReader( () => window.desktopBridge, ); -/** Read the topology while preserving failures for platform cache policy. */ export function readDesktopSecondaryBootstrapsResult(): Promise { return desktopSecondaryBootstrapsReader.refresh(); }