Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
767545f4ab | ||
|
|
d95c49e548 | ||
|
|
7f9bf5eb90 | ||
|
|
65b0de4694 | ||
|
|
4dc9e9e23c | ||
|
|
b5fb5b1738 | ||
|
|
9f6fa1990f | ||
|
|
8904477f75 | ||
|
|
36d7cdd5db | ||
|
|
22ee2db479 |
No files matched your search
@@ -17,8 +17,9 @@ pre-1.0
|
|||||||
|
|
||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
None: no implementation work is open. The cache design,
|
None: no implementation work is open. The check of quak against a live Ente
|
||||||
https://git.eeqj.de/sneak/quak/issues/36, waits on sneak's review.
|
account, https://git.eeqj.de/sneak/quak/issues/187, needs an account only sneak
|
||||||
|
has, so it waits on him.
|
||||||
|
|
||||||
Tagging and releases are decided by sneak alone, and happen only when he
|
Tagging and releases are decided by sneak alone, and happen only when he
|
||||||
declares one.
|
declares one.
|
||||||
@@ -34,6 +35,10 @@ declares one.
|
|||||||
count those in flight. Each worker checks the backup's lock before it takes
|
count those in flight. Each worker checks the backup's lock before it takes
|
||||||
the next file, so once the lock is lost no further original starts.
|
the next file, so once the lock is lost no further original starts.
|
||||||
|
|
||||||
|
- 2026-10-14: Next Step said the cache design, issue 36, waits on sneak's
|
||||||
|
review, though that issue closed on 2026-10-03 (issue 194). It now names the
|
||||||
|
issues open on the tracker.
|
||||||
|
|
||||||
- 2026-10-14: A backup whose lock is lost no longer dies with a stack trace
|
- 2026-10-14: A backup whose lock is lost no longer dies with a stack trace
|
||||||
(issue 184). `lockBackupDirectory` gives proper-lockfile an `onCompromised`
|
(issue 184). `lockBackupDirectory` gives proper-lockfile an `onCompromised`
|
||||||
that records why the lock was lost instead of throwing from its timer, and
|
that records why the lock was lost instead of throwing from its timer, and
|
||||||
|
|||||||
@@ -397,19 +397,33 @@ describe("lib.backup()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// A throw from `onProgress` fails the backup: `lib.backup()` rejects with
|
// A throw from `onProgress` fails the backup: `lib.backup()` rejects with
|
||||||
// it. It rejects only once it has finished with every original it was
|
// it. From then on the backup starts no further original. It finishes the
|
||||||
// working on, so nothing is written into the backup after it has ended
|
// ones it is working on, and rejects only once they are in place, so
|
||||||
// and removed its lock. Here, with a content pool of eight, all eight
|
// nothing is written into the backup after it has ended and removed its
|
||||||
// downloads wait at once. Photo 103's is let go first and answered 404,
|
// lock. Here the first five downloads wait. Photo 103's is let go first
|
||||||
// and the line reporting that failure throws while the other seven wait.
|
// and answered 404, and the line reporting that failure throws while the
|
||||||
it("when onProgress throws, rejects only once the originals it was working on are in place", async () => {
|
// other four wait. Only those five downloads are held: a backup that went
|
||||||
|
// on would store the other three photos.
|
||||||
|
it("when onProgress throws, starts no further original, and rejects once the originals it was working on are in place", async () => {
|
||||||
const dir = mkdtempSync(join(root, "backup-"));
|
const dir = mkdtempSync(join(root, "backup-"));
|
||||||
const holding = holdingServer(103);
|
const downloads: number[] = [];
|
||||||
const lib = await openLibrary(
|
const held: { id: number; release: () => void }[] = [];
|
||||||
dir,
|
const fetch: typeof globalThis.fetch = async (input, init) => {
|
||||||
holding.fetch,
|
const id = downloadOf(input);
|
||||||
new RequestPools({ contentConcurrency: 8 }),
|
if (id !== undefined) {
|
||||||
);
|
downloads.push(id);
|
||||||
|
if (downloads.length <= 5) {
|
||||||
|
await new Promise<void>((release) =>
|
||||||
|
held.push({ id, release }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (id === 103) {
|
||||||
|
return new Response("not found", { status: 404 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return server(input, init);
|
||||||
|
};
|
||||||
|
const lib = await openLibrary(dir, fetch);
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
try {
|
try {
|
||||||
const backup = lib.backup({
|
const backup = lib.backup({
|
||||||
@@ -432,12 +446,12 @@ describe("lib.backup()", () => {
|
|||||||
return err;
|
return err;
|
||||||
});
|
});
|
||||||
|
|
||||||
await vi.waitFor(() => expect(holding.held).toHaveLength(8), {
|
await vi.waitFor(() => expect(held).toHaveLength(5), {
|
||||||
interval: 5,
|
interval: 5,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
});
|
});
|
||||||
const at103 = holding.held.findIndex((h) => h.id === 103);
|
const at103 = held.findIndex((h) => h.id === 103);
|
||||||
holding.held.splice(at103, 1)[0]!.release();
|
held.splice(at103, 1)[0]!.release();
|
||||||
await vi.waitFor(
|
await vi.waitFor(
|
||||||
() =>
|
() =>
|
||||||
expect(
|
expect(
|
||||||
@@ -445,14 +459,13 @@ describe("lib.backup()", () => {
|
|||||||
).toBe(true),
|
).toBe(true),
|
||||||
{ interval: 5, timeout: 5000 },
|
{ interval: 5, timeout: 5000 },
|
||||||
);
|
);
|
||||||
for (const download of holding.held) download.release();
|
for (const download of held) download.release();
|
||||||
|
|
||||||
expect(await rejection).toEqual(
|
expect(await rejection).toEqual(
|
||||||
new Error("the progress output is closed"),
|
new Error("the progress output is closed"),
|
||||||
);
|
);
|
||||||
expect(storedWhenRejected).toEqual(
|
expect(downloads.sort()).toEqual([101, 102, 103, 104, 105]);
|
||||||
PHOTO_IDS.filter((id) => id !== 103),
|
expect(storedWhenRejected).toEqual([101, 102, 104, 105]);
|
||||||
);
|
|
||||||
expect(lockedWhenRejected).toBe(false);
|
expect(lockedWhenRejected).toBe(false);
|
||||||
} finally {
|
} finally {
|
||||||
await lib.close();
|
await lib.close();
|
||||||
|
|||||||
@@ -705,9 +705,10 @@ describe("a backup whose lock is lost", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The backup downloads up to five originals at once, so all three are
|
// The backup downloads up to five originals at once, so all three are
|
||||||
// under way when the lock is lost, and beach.jpg is stored. `quak backup`
|
// under way when the lock is lost, and beach.jpg is stored. The backup
|
||||||
// prints why as one line, with no stack trace, and exits 1.
|
// then stops before it fetches ML data. `quak backup` prints why as one
|
||||||
it("stops quak backup before its next file when backup.lock is removed, and exits 1", async () => {
|
// line, with no stack trace, and exits 1.
|
||||||
|
it("stops quak backup before it fetches ML data when backup.lock is removed, and exits 1", async () => {
|
||||||
const result = backup(dir, {}, fetch);
|
const result = backup(dir, {}, fetch);
|
||||||
await heldRequested;
|
await heldRequested;
|
||||||
rmSync(lockPath, { recursive: true });
|
rmSync(lockPath, { recursive: true });
|
||||||
@@ -722,7 +723,6 @@ describe("a backup whose lock is lost", () => {
|
|||||||
"Fetching original beach.jpg (100)...\n" +
|
"Fetching original beach.jpg (100)...\n" +
|
||||||
"Fetching original IMG_0002.HEIC (300)...\n" +
|
"Fetching original IMG_0002.HEIC (300)...\n" +
|
||||||
"Fetching original toast.mp4 (200)...\n" +
|
"Fetching original toast.mp4 (200)...\n" +
|
||||||
"Fetching ML data...\n" +
|
|
||||||
`quak: lost the lock on ${dir}: ENOENT: no such file or directory, stat '${lockPath}'\n`,
|
`quak: lost the lock on ${dir}: ENOENT: no such file or directory, stat '${lockPath}'\n`,
|
||||||
);
|
);
|
||||||
expect(downloads.sort()).toEqual([100, 200, 300]);
|
expect(downloads.sort()).toEqual([100, 200, 300]);
|
||||||
|
|||||||
Reference in new issue
Block a user