Fix settings update deadlock
This commit is contained in:
parent
75826c242b
commit
6201fd9965
2 changed files with 37 additions and 10 deletions
|
|
@ -37,3 +37,23 @@ describe("Settings logging", () => {
|
||||||
assert.equal(saved, 9);
|
assert.equal(saved, 9);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("owns settings arrays instead of sharing them with callers and listeners", async () => {
|
||||||
|
const original = ["original"];
|
||||||
|
const settings = new Settings(
|
||||||
|
new Logger(),
|
||||||
|
{ ignorePatterns: original },
|
||||||
|
async () => {}
|
||||||
|
);
|
||||||
|
original.push("external mutation");
|
||||||
|
assert.deepEqual(settings.getSettings().ignorePatterns, ["original"]);
|
||||||
|
const update = ["updated"];
|
||||||
|
await settings.setSettings({ ignorePatterns: update });
|
||||||
|
update.push("external mutation");
|
||||||
|
assert.deepEqual(settings.getSettings().ignorePatterns, ["updated"]);
|
||||||
|
settings.onSettingsChanged.add((_, old) => {
|
||||||
|
old.ignorePatterns.push("listener mutation");
|
||||||
|
});
|
||||||
|
await settings.setSettings({ maxFileSizeMB: 12 });
|
||||||
|
assert.deepEqual(settings.getSettings().ignorePatterns, ["updated"]);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export class Settings {
|
||||||
) {
|
) {
|
||||||
this.settings = {
|
this.settings = {
|
||||||
...DEFAULT_SETTINGS,
|
...DEFAULT_SETTINGS,
|
||||||
...(initialState ?? {})
|
...structuredClone(initialState ?? {})
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
|
|
@ -76,20 +76,27 @@ export class Settings {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setSettings(value: Partial<SyncSettings>): Promise<void> {
|
public async setSettings(
|
||||||
await this.lock.withLock(async () => {
|
value: Partial<SyncSettings>,
|
||||||
|
notify = true
|
||||||
|
): Promise<void> {
|
||||||
|
const update = structuredClone(value);
|
||||||
|
|
||||||
|
const change = await this.lock.withLock(async () => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Updating settings with: ${JSON.stringify(redactSettings(value))}`
|
`Updating settings with: ${JSON.stringify(redactSettings(update))}`
|
||||||
);
|
);
|
||||||
const oldSettings = this.settings;
|
|
||||||
const next = { ...this.settings, ...value };
|
const oldSettings = this.getSettings();
|
||||||
|
const next = { ...this.settings, ...update };
|
||||||
await this.saveData(next);
|
await this.saveData(next);
|
||||||
this.settings = next;
|
this.settings = next;
|
||||||
|
|
||||||
await this.onSettingsChanged.triggerAsync(
|
return [this.getSettings(), oldSettings] as const;
|
||||||
this.getSettings(),
|
|
||||||
oldSettings
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listeners can themselves update settings. Never await them while
|
||||||
|
// holding a lock needed by the operation they invoke.
|
||||||
|
if (notify) await this.onSettingsChanged.triggerAsync(...change);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue