fix(desktop): make local data reset reliable
This commit is contained in:
1 parent
f250c63aa5
commit
bd6d030239
3 files changed
+72
-24
No files matched your search
@@ -819,34 +819,56 @@ app.whenReady().then(async () => {
|
||||
if (requestedUserDataDirectory) throw new Error("Local data reset is unavailable with a custom user-data directory");
|
||||
if (localDataResetScheduled) return { scheduled: true };
|
||||
|
||||
localDataResetScheduled = true;
|
||||
isQuitting = true;
|
||||
shutdownCleanupStarted = true;
|
||||
if (sidecarRestartTimer) {
|
||||
clearTimeout(sidecarRestartTimer);
|
||||
sidecarRestartTimer = null;
|
||||
}
|
||||
tray?.destroy();
|
||||
|
||||
try {
|
||||
await stopSidecar();
|
||||
await Promise.allSettled([
|
||||
session.defaultSession.clearStorageData(),
|
||||
session.defaultSession.clearCache(),
|
||||
]);
|
||||
scheduleMacLocalDataReset({
|
||||
await scheduleMacLocalDataReset({
|
||||
appDataDirectory: app.getPath("appData"),
|
||||
executablePath: process.execPath,
|
||||
executablePath: app.getPath("exe"),
|
||||
parentPid: process.pid,
|
||||
userDataDirectory: app.getPath("userData"),
|
||||
});
|
||||
} catch (error) {
|
||||
localDataResetScheduled = false;
|
||||
isQuitting = false;
|
||||
shutdownCleanupStarted = false;
|
||||
await writeDiagnostic("local-data-reset.schedule-failed", {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Only begin shutting down once the detached reset helper has definitely
|
||||
// started. From this point on, exiting lets that helper remove userData and
|
||||
// relaunch the app, so non-critical cleanup failures must not strand the
|
||||
// application in a half-stopped state.
|
||||
localDataResetScheduled = true;
|
||||
isQuitting = true;
|
||||
shutdownCleanupStarted = true;
|
||||
const forcedExitTimer = setTimeout(() => app.exit(0), 5000);
|
||||
forcedExitTimer.unref();
|
||||
if (sidecarRestartTimer) {
|
||||
clearTimeout(sidecarRestartTimer);
|
||||
sidecarRestartTimer = null;
|
||||
}
|
||||
try {
|
||||
tray?.destroy();
|
||||
tray = null;
|
||||
} catch (error) {
|
||||
void writeDiagnostic("local-data-reset.tray-cleanup-failed", {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
await stopSidecar().catch((error) => writeDiagnostic("local-data-reset.sidecar-stop-failed", {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
}));
|
||||
const storageResults = await Promise.allSettled([
|
||||
Promise.resolve().then(() => session.defaultSession.clearStorageData()),
|
||||
Promise.resolve().then(() => session.defaultSession.clearCache()),
|
||||
]);
|
||||
const storageFailure = storageResults.find((result) => result.status === "rejected");
|
||||
if (storageFailure) {
|
||||
void writeDiagnostic("local-data-reset.storage-cleanup-failed", {
|
||||
message: storageFailure.reason instanceof Error ? storageFailure.reason.message : String(storageFailure.reason),
|
||||
});
|
||||
}
|
||||
|
||||
clearTimeout(forcedExitTimer);
|
||||
setTimeout(() => app.exit(0), 50).unref();
|
||||
return { scheduled: true };
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ parent_pid="$1"
|
||||
user_data_dir="$2"
|
||||
application_path="$3"
|
||||
attempt=0
|
||||
while /bin/kill -0 "$parent_pid" 2>/dev/null && [ "$attempt" -lt 300 ]; do
|
||||
while /bin/kill -0 "$parent_pid" 2>/dev/null && [ "$attempt" -lt 3000 ]; do
|
||||
/bin/sleep 0.1
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
@@ -49,7 +49,7 @@ export const macApplicationBundlePath = (executablePath) => {
|
||||
throw new Error("EdgeEver must be running from a macOS application bundle");
|
||||
};
|
||||
|
||||
export const scheduleMacLocalDataReset = ({
|
||||
export const scheduleMacLocalDataReset = async ({
|
||||
appDataDirectory,
|
||||
executablePath,
|
||||
parentPid,
|
||||
@@ -73,6 +73,10 @@ export const scheduleMacLocalDataReset = ({
|
||||
stdio: "ignore",
|
||||
},
|
||||
);
|
||||
await new Promise((resolveSpawn, rejectSpawn) => {
|
||||
helper.once("spawn", resolveSpawn);
|
||||
helper.once("error", rejectSpawn);
|
||||
});
|
||||
helper.unref();
|
||||
return { applicationPath, target };
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { EventEmitter } from "node:events";
|
||||
import {
|
||||
macApplicationBundlePath,
|
||||
managedUserDataDirectory,
|
||||
@@ -32,16 +33,19 @@ describe("desktop local data reset", () => {
|
||||
expect(() => macApplicationBundlePath("/usr/local/bin/edgeever")).toThrow();
|
||||
});
|
||||
|
||||
test("passes paths as shell arguments instead of interpolating them", () => {
|
||||
test("passes paths as shell arguments instead of interpolating them", async () => {
|
||||
let invocation;
|
||||
let unrefCalled = false;
|
||||
const result = scheduleMacLocalDataReset({
|
||||
const resultPromise = scheduleMacLocalDataReset({
|
||||
appDataDirectory: "/Users/example/Library/Application Support",
|
||||
executablePath: "/Applications/EdgeEver.app/Contents/MacOS/EdgeEver",
|
||||
parentPid: 42,
|
||||
spawnProcess: (...args) => {
|
||||
invocation = args;
|
||||
return { unref: () => { unrefCalled = true; } };
|
||||
const helper = new EventEmitter();
|
||||
helper.unref = () => { unrefCalled = true; };
|
||||
queueMicrotask(() => helper.emit("spawn"));
|
||||
return helper;
|
||||
},
|
||||
userDataDirectory: "/Users/example/Library/Application Support/EdgeEver data",
|
||||
});
|
||||
@@ -55,10 +59,28 @@ describe("desktop local data reset", () => {
|
||||
expect(invocation[1][1]).not.toContain("/Applications/EdgeEver.app");
|
||||
expect(invocation[1][1]).not.toContain("EdgeEver data");
|
||||
expect(invocation[2]).toEqual({ detached: true, stdio: "ignore" });
|
||||
const result = await resultPromise;
|
||||
expect(unrefCalled).toBe(true);
|
||||
expect(result).toEqual({
|
||||
applicationPath: "/Applications/EdgeEver.app",
|
||||
target: "/Users/example/Library/Application Support/EdgeEver data",
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects when the reset helper cannot be started", async () => {
|
||||
const resultPromise = scheduleMacLocalDataReset({
|
||||
appDataDirectory: "/Users/example/Library/Application Support",
|
||||
executablePath: "/Applications/EdgeEver.app/Contents/MacOS/EdgeEver",
|
||||
parentPid: 42,
|
||||
spawnProcess: () => {
|
||||
const helper = new EventEmitter();
|
||||
helper.unref = () => {};
|
||||
queueMicrotask(() => helper.emit("error", new Error("spawn failed")));
|
||||
return helper;
|
||||
},
|
||||
userDataDirectory: "/Users/example/Library/Application Support/EdgeEver",
|
||||
});
|
||||
|
||||
await expect(resultPromise).rejects.toThrow("spawn failed");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user