om-memory-system 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,6 +70,17 @@ export declare const START_LOCK_STALE_MS = 20000;
70
70
  export declare function startLockPath(home?: string): string;
71
71
  /** Lock files are private to the user (0600 in a 0700 folder), like the token file. */
72
72
  export declare const nodeLockFs: LockFs;
73
+ /**
74
+ * `nodeLockFs.replace`: write the temp file, rename it over the target, and
75
+ * when the rename reports ENOENT for a temp file that was just written,
76
+ * rewrite it and try again, up to `rewrites` times.
77
+ */
78
+ export declare function replaceRetrying(path: string, text: string, deps?: {
79
+ mkdir?: (dir: string) => void;
80
+ write?: (path: string, text: string) => void;
81
+ rename?: (from: string, to: string) => void;
82
+ rewrites?: number;
83
+ }, rewrites?: number): void;
73
84
  /** `renameSync` that retries while Windows reports the target busy. */
74
85
  export declare function renameRetrying(from: string, to: string, rename?: (from: string, to: string) => void, platform?: NodeJS.Platform, wait?: (ms: number) => void): void;
75
86
  /** Remove the start lock when it names `pid`. A restart copy calls this once it owns the port. */
@@ -70,16 +70,45 @@ export const nodeLockFs = {
70
70
  }
71
71
  },
72
72
  replace(path, text) {
73
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
74
- const temp = `${path}.${process.pid}.tmp`;
75
- writeFileSync(temp, text, { mode: 0o600 });
76
- renameRetrying(temp, path);
73
+ replaceRetrying(path, text);
77
74
  },
78
75
  };
79
76
  // Windows can refuse a link or rename while another process has the file open.
80
77
  // Bound retries so a permanent permission error still reaches the caller.
81
78
  const WINDOWS_FILE_RETRY_WAITS_MS = [1, 2, 5, 10, 20, 50, 100, 200];
82
79
  const WINDOWS_FILE_BUSY_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
80
+ /**
81
+ * How many times `replaceRetrying` rewrites its temp file when `rename`
82
+ * reports ENOENT for a file that was just written. Bun's rename can
83
+ * intermittently do that (see docs/tdr/030-intermittent-bun-rename-failure.md),
84
+ * which made tests/web-ensure.test.ts fail at random under load. Rewriting
85
+ * and retrying heals that without weakening a genuine failure, which keeps
86
+ * failing past this budget and still reaches the caller.
87
+ */
88
+ const REPLACE_ENOENT_REWRITES = 3;
89
+ /**
90
+ * `nodeLockFs.replace`: write the temp file, rename it over the target, and
91
+ * when the rename reports ENOENT for a temp file that was just written,
92
+ * rewrite it and try again, up to `rewrites` times.
93
+ */
94
+ export function replaceRetrying(path, text, deps = {}, rewrites = deps.rewrites ?? REPLACE_ENOENT_REWRITES) {
95
+ const mkdir = deps.mkdir ?? ((dir) => mkdirSync(dir, { recursive: true, mode: 0o700 }));
96
+ const write = deps.write ?? ((file, body) => writeFileSync(file, body, { mode: 0o600 }));
97
+ const rename = deps.rename ?? ((from, to) => renameRetrying(from, to));
98
+ mkdir(dirname(path));
99
+ const temp = `${path}.${process.pid}.tmp`;
100
+ for (let attempt = 0;; attempt++) {
101
+ write(temp, text);
102
+ try {
103
+ rename(temp, path);
104
+ return;
105
+ }
106
+ catch (error) {
107
+ if (error.code !== "ENOENT" || attempt >= rewrites)
108
+ throw error;
109
+ }
110
+ }
111
+ }
83
112
  /** `renameSync` that retries while Windows reports the target busy. */
84
113
  export function renameRetrying(from, to, rename = renameSync, platform = process.platform, wait = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)) {
85
114
  for (let attempt = 0;; attempt++) {