mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-30 11:49:50 +02:00
[eric] crash: quiesce+detach outgoing webviews before dashboard-switch resetLayout (it unmounted every app/browser webview in one frame w/ CDP attached = GPU/browser SIGSEGV, the 'navigate away kills the app' self-quit)
This commit is contained in:
@@ -29,6 +29,7 @@ import { fetchProviderHealth } from '@/shared/state/subscriptionsSlice';
|
||||
import { dashboardWs } from '@/shared/ws/WebSocketManager';
|
||||
import { initBrowserCommandHandler } from '@/shared/browserCommandHandler';
|
||||
import { getKeepAliveBrowserIds } from '@/shared/browserFocus';
|
||||
import { prepareDashboardSwitch } from '@/shared/dashboardSwitchTeardown';
|
||||
import { clearPendingBrowserUrl, clearPendingFocusAgentId } from '@/shared/state/tempStateSlice';
|
||||
import { API_BASE } from '@/shared/config';
|
||||
import type { CanvasActions } from '../interaction/useCanvasControls';
|
||||
@@ -118,10 +119,16 @@ export function useDashboardLifecycle({
|
||||
hasFittedRef.current = false;
|
||||
restoredExpandedRef.current = false;
|
||||
setOutputsRefetched(false);
|
||||
dispatch(resetLayout({ keepBrowserIds: getKeepAliveBrowserIds() }));
|
||||
// CRITICAL path: these populate the cards the user expects to see on first paint. Don't defer.
|
||||
dispatch(fetchSessions({ dashboardId }));
|
||||
dispatch(fetchLayout({ dashboardId }));
|
||||
const keep = getKeepAliveBrowserIds();
|
||||
// Quiesce + CDP-detach the OUTGOING dashboard's webviews serially BEFORE the store reset
|
||||
// unmounts them all in one frame (the "navigate away kills the app" self-quit). Bounded +
|
||||
// fail-open, so a wedged webview can never block the switch. See dashboardSwitchTeardown.
|
||||
void prepareDashboardSwitch(keep).finally(() => {
|
||||
dispatch(resetLayout({ keepBrowserIds: keep }));
|
||||
// CRITICAL path: these populate the cards the user expects to see on first paint. Don't defer.
|
||||
dispatch(fetchSessions({ dashboardId }));
|
||||
dispatch(fetchLayout({ dashboardId }));
|
||||
});
|
||||
}
|
||||
const cleanupBrowserHandler = initBrowserCommandHandler();
|
||||
// Global broadcasts (spawned browser cards) skip the replay log, so a socket gap loses them; a reconnect refetch is the only way they return.
|
||||
|
||||
@@ -116,6 +116,10 @@ export function getWebview(browserId: string, tabId?: string): BrowserWebview |
|
||||
return registry.get(makeKey(browserId, resolvedTabId));
|
||||
}
|
||||
|
||||
export function getAllBrowserIds(): string[] {
|
||||
return [...new Set([...registry.keys()].map((k) => k.split(':')[0]))];
|
||||
}
|
||||
|
||||
export function getBrowserWebviews(browserId: string): BrowserWebview[] {
|
||||
const out: BrowserWebview[] = [];
|
||||
for (const [key, wv] of registry.entries()) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { getAllViewOutputIds } from '@/shared/viewWebviewRegistry';
|
||||
import { getAllBrowserIds } from '@/shared/browserRegistry';
|
||||
import { quiesceViewWebview } from '@/shared/viewTeardown';
|
||||
import { detachBrowserCdp } from '@/shared/browserTeardown';
|
||||
|
||||
// Switching dashboards clears every card from the store in ONE reducer (resetLayout), so React
|
||||
// unmounts all the outgoing app + browser <webview>s in a single frame. Ripping several live GPU
|
||||
// surfaces out at once (app previews) or unmounting a browser with its CDP debugger still attached
|
||||
// piles up "non-existent mailbox" errors and SIGSEGVs the GPU/browser process, taking the whole app
|
||||
// down with no crash dump (the "navigate away and it quits itself" bug). Quiesce + detach the
|
||||
// outgoing webviews ONE AT A TIME first, so only trivial surfaces are left to tear down. Bounded per
|
||||
// item (the helpers self-cap), fail-open, and keep-alive browsers are skipped so they survive the
|
||||
// switch with their session intact.
|
||||
export async function prepareDashboardSwitch(keepBrowserIds: string[]): Promise<void> {
|
||||
const keep = new Set(keepBrowserIds);
|
||||
for (const outputId of getAllViewOutputIds()) {
|
||||
await quiesceViewWebview(outputId);
|
||||
}
|
||||
for (const browserId of getAllBrowserIds()) {
|
||||
if (keep.has(browserId)) continue;
|
||||
await detachBrowserCdp(browserId);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,10 @@ export function registerViewWebview(outputId: string, wv: ViewWebview): void {
|
||||
registry.set(outputId, wv);
|
||||
}
|
||||
|
||||
export function getAllViewOutputIds(): string[] {
|
||||
return [...registry.keys()];
|
||||
}
|
||||
|
||||
export function unregisterViewWebview(outputId: string): void {
|
||||
registry.delete(outputId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user