feat: optimize IPC initialization and context management for improved app performance
- Deferred IPC-heavy initialization to occur after the first paint to prevent app freezing on Windows. - Staggered notification listener setup to avoid saturating the UV thread pool during startup. - Updated context system initialization to be lazy, ensuring local context is always ready without upfront costs. - Enhanced data fetching sequence to reduce simultaneous IPC calls, improving overall responsiveness.
This commit is contained in:
parent
1a928a68de
commit
736ec470d9
3 changed files with 33 additions and 24 deletions
|
|
@ -23,27 +23,28 @@ export const App = (): React.JSX.Element => {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Initialize context system first, then notification listeners.
|
// Defer IPC-heavy initialization to after the first paint.
|
||||||
// Staggered to avoid flooding the main process with 6+ simultaneous IPC
|
// On Windows, firing 6+ IPC calls simultaneously at startup saturates the
|
||||||
// calls at startup, which saturates the UV thread pool on Windows.
|
// UV thread pool (4 threads by default), causing the app to freeze.
|
||||||
|
// Context system init is skipped here — local context is ready by default,
|
||||||
|
// and SSH context is initialized lazily when SSH connects (see below).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let notificationCleanup: (() => void) | undefined;
|
let cleanup: (() => void) | undefined;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
void useStore
|
cleanup = initializeNotificationListeners();
|
||||||
.getState()
|
}, 100);
|
||||||
.initializeContextSystem()
|
return () => {
|
||||||
.finally(() => {
|
clearTimeout(timer);
|
||||||
// Start notification listeners after context system is ready
|
cleanup?.();
|
||||||
notificationCleanup = initializeNotificationListeners();
|
};
|
||||||
});
|
|
||||||
|
|
||||||
return () => notificationCleanup?.();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Refresh available contexts when SSH connection state changes
|
// Initialize context system lazily when SSH connection state changes.
|
||||||
|
// Local-only users never pay the cost of IndexedDB init + context IPC calls.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!api.ssh?.onStatus) return;
|
if (!api.ssh?.onStatus) return;
|
||||||
const cleanup = api.ssh.onStatus(() => {
|
const cleanup = api.ssh.onStatus(() => {
|
||||||
|
void useStore.getState().initializeContextSystem();
|
||||||
void useStore.getState().fetchAvailableContexts();
|
void useStore.getState().fetchAvailableContexts();
|
||||||
});
|
});
|
||||||
return cleanup;
|
return cleanup;
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,18 @@ export function initializeNotificationListeners(): () => void {
|
||||||
cleanupFns.push(() => {
|
cleanupFns.push(() => {
|
||||||
useStore.getState().unsubscribeProvisioningProgress();
|
useStore.getState().unsubscribeProvisioningProgress();
|
||||||
});
|
});
|
||||||
void useStore.getState().fetchTeams();
|
// Stagger IPC data fetches to avoid saturating the UV thread pool on Windows.
|
||||||
|
// Each fetch triggers file I/O in the main process; firing them all at once
|
||||||
|
// blocks all 4 default UV threads simultaneously, freezing the app.
|
||||||
|
void useStore
|
||||||
|
.getState()
|
||||||
|
.fetchTeams()
|
||||||
|
.finally(() => {
|
||||||
|
void useStore.getState().fetchNotifications();
|
||||||
|
if (api.cliInstaller) {
|
||||||
|
void useStore.getState().fetchCliStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
const pendingSessionRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
const pendingSessionRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||||
const pendingProjectRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
const pendingProjectRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||||
let teamRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
let teamRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
@ -168,8 +179,7 @@ export function initializeNotificationListeners(): () => void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch after listeners are attached so startup events do not get overwritten by a stale response.
|
// fetchNotifications() is called in the staggered init chain above (after fetchTeams).
|
||||||
void useStore.getState().fetchNotifications();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a session is visible in any pane (not just the focused pane's active tab).
|
* Check if a session is visible in any pane (not just the focused pane's active tab).
|
||||||
|
|
@ -363,10 +373,7 @@ export function initializeNotificationListeners(): () => void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-check CLI status on startup
|
// fetchCliStatus() is called in the staggered init chain above (after fetchTeams).
|
||||||
if (api.cliInstaller) {
|
|
||||||
void useStore.getState().fetchCliStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for CLI installer progress events from main process
|
// Listen for CLI installer progress events from main process
|
||||||
let cliCompletedRevertTimer: ReturnType<typeof setTimeout> | null = null;
|
let cliCompletedRevertTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
|
||||||
|
|
@ -226,11 +226,12 @@ function captureSnapshot(state: AppState, contextId: string): ContextSnapshot {
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
export const createContextSlice: StateCreator<AppState, [], [], ContextSlice> = (set, get) => ({
|
export const createContextSlice: StateCreator<AppState, [], [], ContextSlice> = (set, get) => ({
|
||||||
// Initial state
|
// Initial state — local context is always ready, no initialization needed.
|
||||||
|
// initializeContextSystem() is called lazily when SSH connects.
|
||||||
activeContextId: 'local',
|
activeContextId: 'local',
|
||||||
isContextSwitching: false,
|
isContextSwitching: false,
|
||||||
targetContextId: null,
|
targetContextId: null,
|
||||||
contextSnapshotsReady: false,
|
contextSnapshotsReady: true,
|
||||||
availableContexts: [{ id: 'local', type: 'local' as const }],
|
availableContexts: [{ id: 'local', type: 'local' as const }],
|
||||||
|
|
||||||
// Initialize context system (called once on app mount)
|
// Initialize context system (called once on app mount)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue