perf: reduce desktop quit risk polling

Refresh quit risk only when quitting
Use in-process status for Electron local server
Avoid repeated scheduled task status scans
This commit is contained in:
Bohdan Triapitsyn
2026-04-30 13:54:58 +03:00
parent bbd83d60c6
commit 8152bd7808
4 changed files with 50 additions and 21 deletions
+20 -21
View File
@@ -128,7 +128,6 @@ const state = {
quitConfirmed: false, quitConfirmed: false,
quitConfirmationPending: false, quitConfirmationPending: false,
installingUpdate: false, installingUpdate: false,
quitRiskPollerStarted: false,
pendingUpdate: null, pendingUpdate: null,
unreachableHosts: new Set(), unreachableHosts: new Set(),
windowCounter: 1, windowCounter: 1,
@@ -138,7 +137,6 @@ const state = {
sshLogs: new Map(), sshLogs: new Map(),
}; };
const QUIT_RISK_POLL_INTERVAL_MS = 5_000;
const quitRisk = { const quitRisk = {
hasActiveTunnel: false, hasActiveTunnel: false,
hasRunningScheduledTasks: false, hasRunningScheduledTasks: false,
@@ -206,6 +204,8 @@ const performConfirmedQuit = () => {
}; };
const requestQuitWithConfirmation = async () => { const requestQuitWithConfirmation = async () => {
await refreshQuitRiskFlags();
if (!shouldRequireQuitConfirmation()) { if (!shouldRequireQuitConfirmation()) {
performConfirmedQuit(); performConfirmedQuit();
return; return;
@@ -247,6 +247,24 @@ const requestQuitWithConfirmation = async () => {
}; };
const refreshQuitRiskFlags = async () => { const refreshQuitRiskFlags = async () => {
if (state.serverHandle && typeof state.serverHandle.getQuitRiskStatus === 'function') {
try {
const status = await state.serverHandle.getQuitRiskStatus();
const scheduled = status?.scheduledTasks;
if (scheduled && typeof scheduled === 'object') {
const enabledCount = Number(scheduled.enabledScheduledTasksCount ?? 0);
const runningCount = Number(scheduled.runningScheduledTasksCount ?? 0);
quitRisk.enabledScheduledTasksCount = Number.isFinite(enabledCount) ? enabledCount : 0;
quitRisk.runningScheduledTasksCount = Number.isFinite(runningCount) ? runningCount : 0;
quitRisk.hasEnabledScheduledTasks = Boolean(scheduled.hasEnabledScheduledTasks) || quitRisk.enabledScheduledTasksCount > 0;
quitRisk.hasRunningScheduledTasks = Boolean(scheduled.hasRunningScheduledTasks) || quitRisk.runningScheduledTasksCount > 0;
}
quitRisk.hasActiveTunnel = Boolean(status?.tunnel?.active);
return;
} catch {
}
}
const base = typeof state.sidecarUrl === 'string' ? state.sidecarUrl.trim().replace(/\/$/, '') : ''; const base = typeof state.sidecarUrl === 'string' ? state.sidecarUrl.trim().replace(/\/$/, '') : '';
if (!base) return; if (!base) return;
@@ -279,24 +297,6 @@ const refreshQuitRiskFlags = async () => {
} }
}; };
const startQuitRiskPoller = () => {
if (process.platform !== 'darwin') return;
if (state.quitRiskPollerStarted) return;
state.quitRiskPollerStarted = true;
const loop = async () => {
while (!state.quitConfirmed && !state.quitRequested) {
await refreshQuitRiskFlags();
if (state.quitConfirmed || state.quitRequested) break;
await new Promise((resolve) => {
const timer = setTimeout(resolve, QUIT_RISK_POLL_INTERVAL_MS);
if (typeof timer?.unref === 'function') timer.unref();
});
}
};
void loop();
};
const settingsFilePath = () => { const settingsFilePath = () => {
if (typeof process.env.OPENCHAMBER_DATA_DIR === 'string' && process.env.OPENCHAMBER_DATA_DIR.trim()) { if (typeof process.env.OPENCHAMBER_DATA_DIR === 'string' && process.env.OPENCHAMBER_DATA_DIR.trim()) {
return path.join(process.env.OPENCHAMBER_DATA_DIR.trim(), 'settings.json'); return path.join(process.env.OPENCHAMBER_DATA_DIR.trim(), 'settings.json');
@@ -2399,7 +2399,6 @@ app.whenReady().then(async () => {
const { initialUrl, localOrigin, bootOutcome } = await resolveInitialUrl(); const { initialUrl, localOrigin, bootOutcome } = await resolveInitialUrl();
await activateMainWindow(initialUrl, localOrigin, bootOutcome); await activateMainWindow(initialUrl, localOrigin, bootOutcome);
startQuitRiskPoller();
// Notify renderer on OS wake-from-sleep so the SSE event pipeline can // Notify renderer on OS wake-from-sleep so the SSE event pipeline can
// reconnect immediately instead of waiting for the heartbeat watchdog. // reconnect immediately instead of waiting for the heartbeat watchdog.
+6
View File
@@ -1233,6 +1233,12 @@ async function main(options = {}) {
getPort: () => tunnelRuntimeContext.getActivePort(), getPort: () => tunnelRuntimeContext.getActivePort(),
getOpenCodePort: () => openCodePort, getOpenCodePort: () => openCodePort,
getTunnelUrl: () => tunnelService.getPublicUrl(), getTunnelUrl: () => tunnelService.getPublicUrl(),
getQuitRiskStatus: () => ({
tunnel: {
active: Boolean(tunnelService.getPublicUrl()),
},
scheduledTasks: scheduledTasksRuntime.getStatus(),
}),
isReady: () => isOpenCodeReady, isReady: () => isOpenCodeReady,
restartOpenCode: () => restartOpenCode(), restartOpenCode: () => restartOpenCode(),
stop: (shutdownOptions = {}) => stop: (shutdownOptions = {}) =>
@@ -156,6 +156,10 @@ export const registerScheduledTaskRoutes = (app, dependencies) => {
app.get('/api/openchamber/scheduled-tasks/status', async (_req, res) => { app.get('/api/openchamber/scheduled-tasks/status', async (_req, res) => {
try { try {
if (typeof scheduledTasksRuntime.getStatus === 'function') {
return res.json(scheduledTasksRuntime.getStatus());
}
const settings = await readSettingsFromDiskMigrated(); const settings = await readSettingsFromDiskMigrated();
const projects = sanitizeProjects(settings?.projects || []); const projects = sanitizeProjects(settings?.projects || []);
@@ -739,11 +739,31 @@ export const createScheduledTasksRuntime = (deps) => {
queue.length = 0; queue.length = 0;
}; };
const getStatus = () => {
let enabledCount = 0;
for (const taskMap of tasksByProject.values()) {
for (const task of taskMap.values()) {
if (task?.enabled) {
enabledCount += 1;
}
}
}
const runningCount = runningTaskKeys.size;
return {
hasEnabledScheduledTasks: enabledCount > 0,
hasRunningScheduledTasks: runningCount > 0,
enabledScheduledTasksCount: enabledCount,
runningScheduledTasksCount: runningCount,
};
};
return { return {
start, start,
stop, stop,
syncAllProjects, syncAllProjects,
syncProject, syncProject,
runNow, runNow,
getStatus,
}; };
}; };