fix: resolve race conditions in blob-store, discovery-cache, and agent-loop
- blob-store: Replace non-atomic check-then-act (existsSync + writeFileSync) with writeFileSync using 'wx' flag for atomic exclusive creation - discovery-cache: Re-read from disk before mutations to avoid stale overwrites, and use temp file + rename for atomic saves - agent-loop: Deep copy messages array in agentLoopContinue to prevent shared reference mutations from affecting the original context
This commit is contained in:
parent
f196309295
commit
806cb76e72
3 changed files with 19 additions and 6 deletions
|
|
@ -118,7 +118,10 @@ export function agentLoopContinue(
|
|||
|
||||
(async () => {
|
||||
const newMessages: AgentMessage[] = [];
|
||||
const currentContext: AgentContext = { ...context };
|
||||
const currentContext: AgentContext = {
|
||||
...context,
|
||||
messages: [...context.messages],
|
||||
};
|
||||
|
||||
stream.push({ type: "agent_start" });
|
||||
stream.push({ type: "turn_start" });
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* provides automatic deduplication across sessions.
|
||||
*/
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, readdirSync, readFileSync, writeFileSync, existsSync, accessSync, unlinkSync, statSync } from "node:fs";
|
||||
import { mkdirSync, readdirSync, readFileSync, writeFileSync, accessSync, unlinkSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const BLOB_PREFIX = "blob:sha256:";
|
||||
|
|
@ -37,8 +37,11 @@ export class BlobStore {
|
|||
},
|
||||
};
|
||||
|
||||
if (!existsSync(blobPath)) {
|
||||
writeFileSync(blobPath, data);
|
||||
try {
|
||||
writeFileSync(blobPath, data, { flag: "wx" }); // Atomic: fails if file exists
|
||||
} catch (err: any) {
|
||||
if (err.code !== "EEXIST") throw err;
|
||||
// File already exists — expected for content-addressed storage
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Stores results at {agentDir}/discovery-cache.json with per-provider TTLs.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { getAgentDir } from "../config.js";
|
||||
import { type DiscoveredModel, getDefaultTTL } from "./model-discovery.js";
|
||||
|
|
@ -35,6 +35,8 @@ export class ModelDiscoveryCache {
|
|||
}
|
||||
|
||||
set(provider: string, models: DiscoveredModel[], ttlMs?: number): void {
|
||||
// Re-read from disk to get the latest state before modifying
|
||||
this.load();
|
||||
this.data.entries[provider] = {
|
||||
models,
|
||||
fetchedAt: Date.now(),
|
||||
|
|
@ -50,6 +52,8 @@ export class ModelDiscoveryCache {
|
|||
}
|
||||
|
||||
clear(provider?: string): void {
|
||||
// Re-read from disk to get the latest state before modifying
|
||||
this.load();
|
||||
if (provider) {
|
||||
delete this.data.entries[provider];
|
||||
} else {
|
||||
|
|
@ -89,7 +93,10 @@ export class ModelDiscoveryCache {
|
|||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
writeFileSync(this.cachePath, JSON.stringify(this.data, null, 2), "utf-8");
|
||||
// Atomic write: write to temp file then rename to avoid partial reads
|
||||
const tmpPath = this.cachePath + ".tmp";
|
||||
writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), "utf-8");
|
||||
renameSync(tmpPath, this.cachePath);
|
||||
} catch {
|
||||
// Silently ignore write failures (read-only FS, permissions, etc.)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue