Fix adapter plugin conflicts caused by concurrent install/reload/delete operations

Add an async mutex (promise-chain FIFO queue) to serialise all adapter
mutation routes (install, reinstall, reload, delete) so that concurrent
requests cannot race on npm, the adapter-plugins.json store, or the
in-memory adapter registry.

Also switch adapter-plugin-store file writes to atomic write-tmp-then-rename
to prevent partial/corrupted reads from concurrent processes.

Includes the packageName.trim() fix for whitespace-induced npm failures.

9 new tests covering mutex serialisation, error recovery, FIFO ordering,
and atomic store operations.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-12 15:54:09 +00:00
parent 9c7a17e16c
commit 92afa0fb67
3 changed files with 410 additions and 198 deletions
+9 -2
View File
@@ -86,7 +86,12 @@ function readStore(): AdapterPluginRecord[] {
function writeStore(records: AdapterPluginRecord[]): void {
ensureDirs();
fs.writeFileSync(ADAPTER_PLUGINS_STORE_PATH, JSON.stringify(records, null, 2), "utf-8");
// Atomic write: write to a temp file in the same directory then rename.
// rename() is atomic on POSIX when source and target are on the same
// filesystem, preventing partial/corrupted reads from concurrent processes.
const tmpPath = `${ADAPTER_PLUGINS_STORE_PATH}.${process.pid}.tmp`;
fs.writeFileSync(tmpPath, JSON.stringify(records, null, 2), "utf-8");
fs.renameSync(tmpPath, ADAPTER_PLUGINS_STORE_PATH);
storeCache = records;
}
@@ -106,7 +111,9 @@ function readSettings(): AdapterSettings {
function writeSettings(settings: AdapterSettings): void {
ensureDirs();
fs.writeFileSync(ADAPTER_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
const tmpPath = `${ADAPTER_SETTINGS_PATH}.${process.pid}.tmp`;
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), "utf-8");
fs.renameSync(tmpPath, ADAPTER_SETTINGS_PATH);
settingsCache = settings;
}