82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import { lstat, readdir, rm } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
createStoreFileLock,
|
|
removeStaleStoreFileLock,
|
|
} from "../../../src/application/StoreFileLock.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
describe("store lock cleanup ownership", () => {
|
|
it.each(["released", "replaced"] as const)(
|
|
"preserves a successor lock when an old %s owner cleans up",
|
|
async (state) => {
|
|
const root = await createTestTempDirectory("rea-store-lock-owner-");
|
|
const path = join(root, "transaction.lock");
|
|
const previous = await createStoreFileLock(path);
|
|
if (state === "released") await previous.release();
|
|
else await rm(path, { recursive: true });
|
|
const successor = await createStoreFileLock(path);
|
|
try {
|
|
await previous.release();
|
|
await expect(lstat(path)).resolves.toBeDefined();
|
|
await expect(createStoreFileLock(path)).rejects.toMatchObject({
|
|
code: "EEXIST",
|
|
});
|
|
} finally {
|
|
await successor.release();
|
|
}
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("store lock acquisition and recovery", () => {
|
|
it("admits one concurrent owner and removes all preparation directories", async () => {
|
|
const root = await createTestTempDirectory("rea-store-lock-contenders-");
|
|
const path = join(root, "transaction.lock");
|
|
const results = await Promise.allSettled(
|
|
Array.from({ length: 8 }, () => createStoreFileLock(path)),
|
|
);
|
|
try {
|
|
expect(
|
|
results.filter((result) => result.status === "fulfilled"),
|
|
).toHaveLength(1);
|
|
for (const result of results)
|
|
if (result.status === "rejected")
|
|
expect(result.reason).toMatchObject({ code: "EEXIST" });
|
|
expect(await readdir(root)).toEqual(["transaction.lock"]);
|
|
expect(await removeStaleStoreFileLock(path)).toBe(false);
|
|
} finally {
|
|
await Promise.all(
|
|
results.map((result) =>
|
|
result.status === "fulfilled" ? result.value.release() : undefined,
|
|
),
|
|
);
|
|
}
|
|
expect(await readdir(root)).toEqual([]);
|
|
});
|
|
|
|
it("recovers a directory lock abandoned by an exited process", async () => {
|
|
const root = await createTestTempDirectory("rea-store-lock-exited-");
|
|
const path = join(root, "transaction.lock");
|
|
await promisify(execFile)(
|
|
process.execPath,
|
|
["tests/fixtures/abandonStoreFileLock.mjs", path],
|
|
{ timeout: 5_000 },
|
|
);
|
|
expect((await lstat(path)).isDirectory()).toBe(true);
|
|
expect(await removeStaleStoreFileLock(path)).toBe(true);
|
|
const successor = await createStoreFileLock(path);
|
|
try {
|
|
expect(await removeStaleStoreFileLock(path)).toBe(false);
|
|
} finally {
|
|
await successor.release();
|
|
}
|
|
expect(await readdir(root)).toEqual([]);
|
|
});
|
|
});
|