





















@@ -8,30 +8,48 @@ type MemoryIndexFileOps = {
88wait: (ms: number) => Promise<void>;
99};
101011-type MoveMemoryIndexFilesOptions = {
11+type MemoryIndexFileOptions = {
1212fileOps?: MemoryIndexFileOps;
1313maxRenameAttempts?: number;
1414renameRetryDelayMs?: number;
15+maxRemoveAttempts?: number;
16+removeRetryDelayMs?: number;
1517};
161819+type ResolvedMemoryIndexFileOptions = Required<MemoryIndexFileOptions>;
20+1721const defaultFileOps: MemoryIndexFileOps = {
1822rename: fs.rename,
1923rm: fs.rm,
2024wait: sleep,
2125};
222623-const transientRenameErrorCodes = new Set(["EBUSY", "EPERM", "EACCES"]);
27+const transientFileErrorCodes = new Set(["EBUSY", "EPERM", "EACCES"]);
2428const defaultMaxRenameAttempts = 6;
2529const defaultRenameRetryDelayMs = 25;
30+const defaultMaxRemoveAttempts = 10;
31+const defaultRemoveRetryDelayMs = 50;
32+33+function isTransientFileError(err: unknown): boolean {
34+return transientFileErrorCodes.has((err as NodeJS.ErrnoException).code ?? "");
35+}
263627-function isTransientRenameError(err: unknown): boolean {
28-return transientRenameErrorCodes.has((err as NodeJS.ErrnoException).code ?? "");
37+function resolveMemoryIndexFileOptions(
38+options: MemoryIndexFileOptions = {},
39+): ResolvedMemoryIndexFileOptions {
40+return {
41+fileOps: options.fileOps ?? defaultFileOps,
42+maxRenameAttempts: Math.max(1, options.maxRenameAttempts ?? defaultMaxRenameAttempts),
43+renameRetryDelayMs: options.renameRetryDelayMs ?? defaultRenameRetryDelayMs,
44+maxRemoveAttempts: Math.max(1, options.maxRemoveAttempts ?? defaultMaxRemoveAttempts),
45+removeRetryDelayMs: options.removeRetryDelayMs ?? defaultRemoveRetryDelayMs,
46+};
2947}
30483149async function renameWithRetry(
3250source: string,
3351target: string,
34-options: Required<MoveMemoryIndexFilesOptions>,
52+options: ResolvedMemoryIndexFileOptions,
3553): Promise<void> {
3654for (let attempt = 1; attempt <= options.maxRenameAttempts; attempt++) {
3755try {
@@ -41,7 +59,7 @@ async function renameWithRetry(
4159if ((err as NodeJS.ErrnoException).code === "ENOENT") {
4260return;
4361}
44-if (!isTransientRenameError(err) || attempt === options.maxRenameAttempts) {
62+if (!isTransientFileError(err) || attempt === options.maxRenameAttempts) {
4563throw err;
4664}
4765await options.fileOps.wait(options.renameRetryDelayMs * attempt);
@@ -53,13 +71,9 @@ async function renameWithRetry(
5371export async function moveMemoryIndexFiles(
5472sourceBase: string,
5573targetBase: string,
56-options: MoveMemoryIndexFilesOptions = {},
74+options: MemoryIndexFileOptions = {},
5775): Promise<void> {
58-const resolvedOptions: Required<MoveMemoryIndexFilesOptions> = {
59-fileOps: options.fileOps ?? defaultFileOps,
60-maxRenameAttempts: Math.max(1, options.maxRenameAttempts ?? defaultMaxRenameAttempts),
61-renameRetryDelayMs: options.renameRetryDelayMs ?? defaultRenameRetryDelayMs,
62-};
76+const resolvedOptions = resolveMemoryIndexFileOptions(options);
6377const suffixes = ["", "-wal", "-shm"];
6478for (const suffix of suffixes) {
6579const source = `${sourceBase}${suffix}`;
@@ -68,12 +82,33 @@ export async function moveMemoryIndexFiles(
6882}
6983}
708471-async function removeMemoryIndexFiles(
85+async function rmWithRetry(path: string, options: ResolvedMemoryIndexFileOptions): Promise<void> {
86+for (let attempt = 1; attempt <= options.maxRemoveAttempts; attempt++) {
87+try {
88+await options.fileOps.rm(path, { force: true });
89+return;
90+} catch (err) {
91+if ((err as NodeJS.ErrnoException).code === "ENOENT") {
92+return;
93+}
94+if (!isTransientFileError(err) || attempt === options.maxRemoveAttempts) {
95+throw err;
96+}
97+await options.fileOps.wait(options.removeRetryDelayMs * attempt);
98+}
99+}
100+throw new Error("rm retry loop exited unexpectedly");
101+}
102+103+export async function removeMemoryIndexFiles(
72104basePath: string,
73-fileOps: MemoryIndexFileOps = defaultFileOps,
105+options: MemoryIndexFileOptions = {},
74106): Promise<void> {
107+const resolvedOptions = resolveMemoryIndexFileOptions(options);
75108const suffixes = ["", "-wal", "-shm"];
76-await Promise.all(suffixes.map((suffix) => fileOps.rm(`${basePath}${suffix}`, { force: true })));
109+for (const suffix of suffixes) {
110+await rmWithRetry(`${basePath}${suffix}`, resolvedOptions);
111+}
77112}
7811379114async function swapMemoryIndexFiles(targetPath: string, tempPath: string): Promise<void> {
@@ -92,13 +127,23 @@ export async function runMemoryAtomicReindex<T>(params: {
92127targetPath: string;
93128tempPath: string;
94129build: () => Promise<T>;
130+beforeTempCleanup?: () => Promise<void> | void;
131+fileOptions?: MemoryIndexFileOptions;
95132}): Promise<T> {
96133try {
97134const result = await params.build();
98135await swapMemoryIndexFiles(params.targetPath, params.tempPath);
99136return result;
100137} catch (err) {
101-await removeMemoryIndexFiles(params.tempPath);
138+try {
139+await params.beforeTempCleanup?.();
140+await removeMemoryIndexFiles(params.tempPath, params.fileOptions);
141+} catch (cleanupErr) {
142+throw new AggregateError(
143+[err, cleanupErr],
144+"memory atomic reindex failed and temp cleanup failed",
145+);
146+}
102147throw err;
103148}
104149}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。