From bb8762e122a62cc626de46fe8c3a3a4b32a1fff7 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 5 May 2026 16:43:38 +0700 Subject: [PATCH] Fix desktop settings first-launch race --- .../src/settings/desktop-settings.test.ts | 16 ++++++++++++++++ .../desktop/src/settings/desktop-settings.ts | 17 ++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/desktop/src/settings/desktop-settings.test.ts b/packages/desktop/src/settings/desktop-settings.test.ts index cd9207c97..0291f220e 100644 --- a/packages/desktop/src/settings/desktop-settings.test.ts +++ b/packages/desktop/src/settings/desktop-settings.test.ts @@ -43,6 +43,22 @@ describe("desktop-settings", () => { expect(persisted.settings).toEqual(DEFAULT_DESKTOP_SETTINGS); }); + it("handles concurrent first-launch reads without racing the settings write", async () => { + const userDataPath = await createTempUserDataDir(); + directories.add(userDataPath); + const store = createDesktopSettingsStore({ userDataPath }); + + const settings = await Promise.all(Array.from({ length: 20 }, () => store.get())); + const persisted = JSON.parse(await readFile(settingsFilePath(userDataPath), "utf8")) as { + settings: DesktopSettings; + }; + const files = await readdir(userDataPath); + + expect(settings).toEqual(Array.from({ length: 20 }, () => DEFAULT_DESKTOP_SETTINGS)); + expect(persisted.settings).toEqual(DEFAULT_DESKTOP_SETTINGS); + expect(files).toEqual(["desktop-settings.json"]); + }); + it("coerces invalid persisted values back to safe defaults", async () => { const userDataPath = await createTempUserDataDir(); directories.add(userDataPath); diff --git a/packages/desktop/src/settings/desktop-settings.ts b/packages/desktop/src/settings/desktop-settings.ts index d2f25d617..cfadc1f6f 100644 --- a/packages/desktop/src/settings/desktop-settings.ts +++ b/packages/desktop/src/settings/desktop-settings.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "node:crypto"; import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import path from "node:path"; @@ -198,13 +199,19 @@ export function createDesktopSettingsStore({ }): DesktopSettingsStore { const filePath = path.join(userDataPath, DESKTOP_SETTINGS_FILENAME); let cachedDocument: PersistedDesktopSettingsDocument | null = null; + let persistQueue: Promise = Promise.resolve(); async function persistDocument(document: PersistedDesktopSettingsDocument): Promise { - await mkdir(userDataPath, { recursive: true }); - const tempFilePath = `${filePath}.tmp`; - await writeFile(tempFilePath, `${JSON.stringify(document, null, 2)}\n`, "utf8"); - await rename(tempFilePath, filePath); - cachedDocument = document; + const write = async () => { + await mkdir(userDataPath, { recursive: true }); + const tempFilePath = `${filePath}.tmp.${process.pid}.${randomUUID()}`; + await writeFile(tempFilePath, `${JSON.stringify(document, null, 2)}\n`, "utf8"); + await rename(tempFilePath, filePath); + cachedDocument = document; + }; + const queued = persistQueue.then(write, write); + persistQueue = queued.catch(() => undefined); + await queued; } async function loadDocument(): Promise {