mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 20:27:44 +02:00
[eric] electron: a crash-recovery dialog that fails tries the recovery once instead of quitting the session (ENG-265)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
// Run: node --test electron/crashRecoveryFallback.test.js
|
||||
//
|
||||
// ENG-265: "the app sometimes dies when submitting the default onboarding input." The issue names
|
||||
// three ways the app can actually die, and one of them is this: the crash-recovery DIALOG throws, and
|
||||
// the handler answers by quitting. That throws the user's whole session away for a reason that says
|
||||
// nothing about whether the app could have recovered, and it leaves no window and no explanation,
|
||||
// which is exactly what "it just died" looks like from the outside.
|
||||
//
|
||||
// main.js is a single 5k-line module with no export surface, so this asserts the shape of the path
|
||||
// rather than executing it. The properties that matter: the failure is recorded, recovery is
|
||||
// attempted before quitting, and the attempt is one-shot so a broken recreate cannot loop.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const src = fs.readFileSync(path.join(__dirname, 'main.js'), 'utf8');
|
||||
const start = src.indexOf('async function showCrashRecoveryOverlay');
|
||||
const body = src.slice(start, src.indexOf('\n}', src.indexOf('crash-recovery-dialog-failed')) + 2);
|
||||
|
||||
test('the dialog failure is written down before anything else happens', () => {
|
||||
// A dead stdout eats console lines (ENG-264), so the console.error alone left this unexplainable.
|
||||
assert.match(body, /writeCrashReport\('crash-recovery-dialog-failed'/);
|
||||
});
|
||||
|
||||
test('a failed dialog attempts recovery before it quits', () => {
|
||||
const failurePath = body.slice(body.indexOf('crash-recovery-dialog-failed'));
|
||||
assert.match(failurePath, /recreateMainWindow\(\)/, 'quitting on a dialog failure is a session thrown away for nothing');
|
||||
assert.ok(
|
||||
failurePath.indexOf('recreateMainWindow()') < failurePath.lastIndexOf('app.quit()'),
|
||||
'the recovery attempt must come before the quit, not after it',
|
||||
);
|
||||
});
|
||||
|
||||
test('the fallback is one-shot, so a broken recreate cannot become a crash loop', () => {
|
||||
assert.match(src, /let p_crashDialogFallbackUsed = false;/);
|
||||
const failurePath = body.slice(body.indexOf('crash-recovery-dialog-failed'));
|
||||
assert.match(failurePath, /if \(p_crashDialogFallbackUsed\)/, 'second time through must just quit');
|
||||
assert.match(failurePath, /p_crashDialogFallbackUsed = true;/);
|
||||
});
|
||||
|
||||
test('a recreate that throws is recorded too, and then quits', () => {
|
||||
const failurePath = body.slice(body.indexOf('crash-recovery-dialog-failed'));
|
||||
assert.match(failurePath, /writeCrashReport\('crash-recovery-recreate-failed'/);
|
||||
});
|
||||
|
||||
test('the user-chose-quit branch is untouched', () => {
|
||||
// Both directions: recovering harder must not take away the user's explicit Quit.
|
||||
assert.match(body, /result\.response === 0[\s\S]*?app\.quit\(\)/, 'Quit must still quit when the user picks it');
|
||||
});
|
||||
+20
-1
@@ -1817,6 +1817,8 @@ function recreateMainWindow() {
|
||||
});
|
||||
}
|
||||
|
||||
// One shot only, so a recreate that itself throws cannot turn into a crash loop.
|
||||
let p_crashDialogFallbackUsed = false;
|
||||
// Crash recovery path B: the cap-exceeded fallback. Native dialog (not a BrowserWindow) so we cannot trigger the same observer-double-add DCHECK that motivated this whole change. User-driven Reload runs in a clean call stack outside the render-process-gone handler.
|
||||
async function showCrashRecoveryOverlay() {
|
||||
try {
|
||||
@@ -1852,7 +1854,24 @@ async function showCrashRecoveryOverlay() {
|
||||
crashCount: rendererCrashTimes.length,
|
||||
});
|
||||
console.error('[main] showCrashRecoveryOverlay failed:', err && err.message);
|
||||
app.quit();
|
||||
// Quitting because a DIALOG failed threw the user's whole session away for a reason that says
|
||||
// nothing about whether the app could have recovered (ENG-265). Try the recovery we would have
|
||||
// offered, once; quit only if that dies too, so a broken recreate cannot become a crash loop.
|
||||
if (p_crashDialogFallbackUsed) {
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
p_crashDialogFallbackUsed = true;
|
||||
try {
|
||||
rendererCrashTimes = [];
|
||||
recreateMainWindow();
|
||||
} catch (again) {
|
||||
crashReports.writeCrashReport('crash-recovery-recreate-failed', {
|
||||
message: String(again && again.message || again),
|
||||
stack: String(again && again.stack || ''),
|
||||
});
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user