quak backup refuses to run while another backup of the same directory runs, exit 2 (closes #169)
check / check (push) Waiting to run
check / check (push) Waiting to run
lib.backup() takes a lock, backup.lock in its download directory, made with proper-lockfile, before its refresh, and removes it when it ends. A second backup of the directory fails at once with an error naming it. quak backup takes the lock itself before it opens its library and passes lockHeld to the backup, so a refused run sends no request; it prints the error as one line and exits 2. A lock untouched for 10 seconds, left by a run that could not remove it, is taken over. Deviation: yarn.lock was regenerated by yarn add in the pinned node image. Judgement call: a run failing at its refresh leaves the directory, empty. Judgement call: a lock removed mid-run stops that run with an uncaught error, the library's default. Model: opus-5-5
This commit was merged in pull request #178.
This commit is contained in:
+76
-1
@@ -43,6 +43,7 @@ import {
|
||||
readlinkSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
utimesSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
@@ -830,7 +831,81 @@ describe("the refresh before a backup", () => {
|
||||
);
|
||||
|
||||
expect(source.originalCalls).toBe(0);
|
||||
expect(existsSync(outDir)).toBe(false);
|
||||
// The backup made the directory for its lock, and removed the lock.
|
||||
expect(readdirSync(outDir)).toEqual([]);
|
||||
await lib.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the backup lock", () => {
|
||||
const lockPath = (outDir: string): string => join(outDir, "backup.lock");
|
||||
|
||||
it("refuses a second backup of the directory while one runs", async () => {
|
||||
await fillCache();
|
||||
const client = new HeldClient();
|
||||
const lib = await openLibrary(stubSource(), client);
|
||||
const outDir = join(root, "backup");
|
||||
// The first backup holds the lock once it starts its refresh, which
|
||||
// `HeldClient` keeps from finishing.
|
||||
let refreshing!: () => void;
|
||||
const started = new Promise<void>((resolve) => {
|
||||
refreshing = resolve;
|
||||
});
|
||||
const first = lib.backup({
|
||||
downloadDirectory: outDir,
|
||||
onProgress: (msg) => {
|
||||
if (msg === "Refreshing library...") refreshing();
|
||||
},
|
||||
});
|
||||
await started;
|
||||
|
||||
await expect(lib.backup({ downloadDirectory: outDir })).rejects.toThrow(
|
||||
`another backup of ${outDir} is running`,
|
||||
);
|
||||
|
||||
client.release();
|
||||
expect((await first).failed).toBe(0);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("releases the lock after a backup succeeds", async () => {
|
||||
const lib = await openLibrary(stubSource());
|
||||
const outDir = join(root, "backup");
|
||||
|
||||
await lib.backup({ downloadDirectory: outDir });
|
||||
|
||||
expect(existsSync(lockPath(outDir))).toBe(false);
|
||||
// So the next backup of the directory runs.
|
||||
expect((await lib.backup({ downloadDirectory: outDir })).skipped).toBe(
|
||||
3,
|
||||
);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("releases the lock after a backup fails", async () => {
|
||||
const lib = await openLibrary(stubSource(), new FailingClient());
|
||||
const outDir = join(root, "backup");
|
||||
|
||||
await expect(lib.backup({ downloadDirectory: outDir })).rejects.toThrow(
|
||||
"HTTP 401 from server",
|
||||
);
|
||||
|
||||
expect(existsSync(lockPath(outDir))).toBe(false);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("takes over a lock left by a run that was killed", async () => {
|
||||
const outDir = join(root, "backup");
|
||||
// A killed run's lock, last touched a minute ago.
|
||||
mkdirSync(lockPath(outDir), { recursive: true });
|
||||
const minuteAgo = new Date(Date.now() - 60_000);
|
||||
utimesSync(lockPath(outDir), minuteAgo, minuteAgo);
|
||||
const lib = await openLibrary(stubSource());
|
||||
|
||||
const result = await lib.backup({ downloadDirectory: outDir });
|
||||
|
||||
expect(result.downloaded).toBe(3);
|
||||
expect(existsSync(lockPath(outDir))).toBe(false);
|
||||
await lib.close();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user