Improve attachment memory safety, downloads, and high-memory alert UX.
All checks were successful
Queue Release Build / prepare (push) Successful in 20s
Deploy Web Apps / deploy (push) Successful in 9m2s
Queue Release Build / build-windows (push) Successful in 28m8s
Queue Release Build / build-linux (push) Successful in 47m26s
Queue Release Build / build-android (push) Successful in 19m52s
Queue Release Build / finalize (push) Successful in 4m42s

Stream large receives to disk with chunk acks to cap renderer RAM, evict
off-screen display blobs, and route exports through a disk-aware download
service. Fix the high-memory dialog (backdrop dismiss, copy, log actions),
allow diagnostics paths in the path jail, and restore persisted image
hydration after reload.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 00:25:22 +02:00
parent f0d79aa627
commit bb0ac930ad
69 changed files with 2306 additions and 430 deletions

View File

@@ -10,19 +10,21 @@ import { resolveReadablePath } from '../path-jail';
import { sumWorkingSetKb } from './process-metrics.rules';
import { isPerfDiagEnabled } from './diagnostics.flags';
import { exceedsHighMemoryThreshold } from './high-memory-alert.rules';
import { buildHighMemoryDiagnosticPayload } from './high-memory-snapshot.rules';
import { collectImmediateRendererSamples } from './immediate-renderer-samples.collector';
import { captureHighMemoryDiagnostics } from './high-memory-capture';
import { collectSessionContext } from './session-context.collector';
import {
clearHighMemoryAlert,
readHighMemoryAlert,
writeHighMemoryAlert
writeHighMemoryAlert,
type HighMemoryAlertRecord
} from './high-memory-alert.store';
import type { PerfDiagEntry } from './diagnostics.models';
import { PerfDiagWriter } from './diagnostics.writer';
const PROCESS_POLL_INTERVAL_MS = 5_000;
export const HIGH_MEMORY_ALERT_PENDING_CHANNEL = 'high-memory-alert-pending';
let activeWriter: PerfDiagWriter | null = null;
let processPollTimer: NodeJS.Timeout | null = null;
let diagnosticsEnabled = false;
@@ -67,6 +69,24 @@ export function ensurePerfDiagIpcRegistered(): void {
return true;
});
ipcMain.handle('export-high-memory-diagnostics', async () => {
const metrics = collectAppMetricsSnapshot();
const totalKb = sumWorkingSetKb(metrics.processes) ?? 0;
const record = await captureHighMemoryDiagnostics({
userDataPath: app.getPath('userData'),
sessionStartedAt,
metrics,
totalWorkingSetKb: totalKb,
writer: activeWriter,
mainWindow: getMainWindow(),
reason: 'manual'
});
await persistAndNotifyHighMemoryAlert(record);
return record;
});
ipcMain.handle('show-log-file-in-folder', async (_event, filePath: string) => {
if (typeof filePath !== 'string' || !filePath.trim()) {
return {
@@ -94,8 +114,48 @@ export function getActivePerfDiagWriter(): PerfDiagWriter | null {
return activeWriter;
}
export function startHighMemoryMonitoring(): void {
ensurePerfDiagIpcRegistered();
if (!sessionStartedAt) {
sessionStartedAt = Date.now();
highMemoryAlertTriggeredThisSession = false;
}
if (processPollTimer) {
return;
}
const sample = (): void => {
try {
const metrics = collectAppMetricsSnapshot();
const totalKb = sumWorkingSetKb(metrics.processes);
if (activeWriter && diagnosticsEnabled) {
activeWriter.append({
collectedAt: metrics.collectedAt,
source: 'main',
type: 'process',
payload: {
totalWorkingSetKb: totalKb,
processes: metrics.processes
}
});
}
void maybeTriggerHighMemoryAlert(metrics, totalKb);
} catch {
// Collector failures must never affect the app.
}
};
sample();
processPollTimer = setInterval(sample, PROCESS_POLL_INTERVAL_MS);
}
export function startPerfDiagnostics(): PerfDiagWriter | null {
ensurePerfDiagIpcRegistered();
startHighMemoryMonitoring();
diagnosticsEnabled = isPerfDiagEnabled(process.env, app.isPackaged);
if (!diagnosticsEnabled) {
@@ -109,10 +169,7 @@ export function startPerfDiagnostics(): PerfDiagWriter | null {
});
activeWriter = writer;
highMemoryAlertTriggeredThisSession = false;
sessionStartedAt = Date.now();
registerProcessCrashHandlers(writer);
startProcessMetricsPolling(writer);
const userDataPath = app.getPath('userData');
@@ -188,14 +245,15 @@ export async function shutdownPerfDiagnostics(): Promise<void> {
}
await activeWriter.flushSnapshot('shutdown');
activeWriter = null;
diagnosticsEnabled = false;
}
export function shutdownHighMemoryMonitoring(): void {
if (processPollTimer) {
clearInterval(processPollTimer);
processPollTimer = null;
}
activeWriter = null;
diagnosticsEnabled = false;
}
function registerProcessCrashHandlers(writer: PerfDiagWriter): void {
@@ -241,34 +299,7 @@ function registerProcessCrashHandlers(writer: PerfDiagWriter): void {
});
}
function startProcessMetricsPolling(writer: PerfDiagWriter): void {
const sample = (): void => {
try {
const metrics = collectAppMetricsSnapshot();
const totalKb = sumWorkingSetKb(metrics.processes);
writer.append({
collectedAt: metrics.collectedAt,
source: 'main',
type: 'process',
payload: {
totalWorkingSetKb: totalKb,
processes: metrics.processes
}
});
void maybeTriggerHighMemoryAlert(writer, metrics, totalKb);
} catch {
// Collector failures must never affect the app.
}
};
sample();
processPollTimer = setInterval(sample, PROCESS_POLL_INTERVAL_MS);
}
async function maybeTriggerHighMemoryAlert(
writer: PerfDiagWriter,
metrics: AppMetricsSnapshot,
totalWorkingSetKb: number | null
): Promise<void> {
@@ -278,51 +309,26 @@ async function maybeTriggerHighMemoryAlert(
highMemoryAlertTriggeredThisSession = true;
const detectedAt = Date.now();
const userDataPath = app.getPath('userData');
const immediateRendererEntries = await collectImmediateRendererSamples(getMainWindow());
const environment = collectSessionContext({
const record = await captureHighMemoryDiagnostics({
userDataPath: app.getPath('userData'),
sessionStartedAt,
userDataPath
metrics,
totalWorkingSetKb: totalWorkingSetKb ?? 0,
writer: activeWriter,
mainWindow: getMainWindow(),
reason: 'threshold'
});
for (const entry of immediateRendererEntries) {
writer.append(entry);
}
await persistAndNotifyHighMemoryAlert(record);
}
writer.append({
collectedAt: detectedAt,
source: 'main',
type: 'environment',
payload: {
...environment
}
});
async function persistAndNotifyHighMemoryAlert(record: HighMemoryAlertRecord): Promise<void> {
await writeHighMemoryAlert(app.getPath('userData'), record);
notifyHighMemoryAlert(record);
}
writer.append({
collectedAt: detectedAt,
source: 'main',
type: 'high-memory',
payload: buildHighMemoryDiagnosticPayload({
detectedAt,
totalWorkingSetKb: totalWorkingSetKb ?? 0,
metrics,
environment,
mainProcessMemory: process.memoryUsage(),
ringEntries: writer.bufferedEntries,
immediateRendererEntries,
sessionId: writer.sessionId
})
});
await writer.flushSnapshot('high-memory-threshold');
await writeHighMemoryAlert(userDataPath, {
logFilePath: writer.snapshotFilePath,
detectedAt,
peakWorkingSetKb: totalWorkingSetKb ?? 0,
sessionId: writer.sessionId
});
function notifyHighMemoryAlert(record: HighMemoryAlertRecord): void {
getMainWindow()?.webContents.send(HIGH_MEMORY_ALERT_PENDING_CHANNEL, record);
}
function normalizeRendererEntry(entry: PerfDiagEntry): PerfDiagEntry {