par2 file lists dropped once their holders are final; late candidates claimable
Test and deploy / test (push) Successful in 47s
Test and deploy / edge (push) Successful in 1m46s
Test and deploy / deploy (push) Successful in 58s

Most par2 sets describe readable uploads whose files are never hashed, so they
were never complete and kept forever (19 GB). Candidates added to a release
after it settled were never marked ready (93,642 since 2026-10-13); the batch
store and merge mark them now, migration 5100 repairs the waiting ones
(LOG.md 2026-10-20 (87)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-20 13:56:57 +02:00
1 parent 4f41303482
commit 8ad5ea162c
7 files changed
+152 -61

No files matched your search

+12
View File
@@ -120,6 +120,18 @@ test.group('BatchStore', (group) => {
assert.equal(Number(chunks!.n), 4)
})
test('candidates added to a settled release (a later range of its upload) are claimable at once', async ({ assert }) => {
await storeIn(GROUP, 1, upload(10, 'Some.Show.S01E02.1080p.mkv', 2, 'Some.Show.S01E02.1080p [1/2]', '2026-10-05T10:00:00Z'))
// Settled before the next range came (the settler marks only what it settles).
await db.from('releases').update({ settled_at: new Date() })
await db.from('probe_candidates').update({ ready: true })
await storeIn(GROUP, 100_001, upload(100_010, 'Some.Show.S01E02.1080p.par2', 1, 'Some.Show.S01E02.1080p [2/2]', '2026-10-05T10:20:00Z'))
const candidates = await db.from('probe_candidates').orderBy('id').select('kind', 'ready')
assert.deepEqual(candidates.map((c) => c.kind), ['par2'])
assert.isTrue(candidates.every((c) => c.ready))
assert.lengthOf(await releasesOf(), 1)
})
test('the same upload in another group (multi-posted) is a release of its own', async ({ assert }) => {
await storeIn(GROUP, 1, upload(10, 'Some.Show.S01E01.1080p.mkv', 2, 'Some.Show.S01E01.1080p [1/1]', '2026-10-05T10:00:00Z'))
await storeIn('alt.binaries.other', 1, upload(10, 'Some.Show.S01E01.1080p.mkv', 2, 'Some.Show.S01E01.1080p [1/1]', '2026-10-05T10:01:00Z', 'alt.binaries.other'))
+41 -10
View File
@@ -53,23 +53,53 @@ test.group('Par2Pruner', (group) => {
const locked = await set('e', e, 2, null)
await db.table('release_verdicts').insert({ release_id: e, rule: 'locked', rule_version: 1 })
// Out of budget: the complete pass's batch, not the judged pass (the next round goes on).
assert.deepEqual(await new Par2Pruner().round(0), { complete: 1, judged: 0, rows: 2 })
await db.from('worker_cursors').where('name', 'par2-prune').delete()
const round = await new Par2Pruner().round()
assert.deepEqual(round, { complete: 0, judged: 1, rows: 2 })
// Final: its holder held by a run of done ranges with hours to spare, nothing hashed (a readable upload's files).
const f = await release()
const final = await set('f', f, 2, null)
const posted = new Date('2024-03-01T12:00:00Z')
const [group] = await db.table('groups').insert({ name: 'alt.binaries.pruned', priority: 1 }).returning('id')
const groupId = Number(group.id)
await db.from('releases').where('id', f).update({ group_id: groupId, posted_at: posted, last_posted_at: posted, first_article: 5000, last_article: 5100 })
const [range] = await db
.table('ranges')
.insert({ group_id: groupId, start_id: 1, end_id: 10000, next_id: 10001, status: 'done', done_at: old, posted_low: new Date('2024-02-28T00:00:00Z'), posted_high: new Date('2024-03-03T00:00:00Z') })
.returning('id')
await db.table('range_blocks').insert({ group_id: groupId, start_id: 1, end_id: 10000, posted_low: new Date('2024-02-28T00:00:00Z'), posted_high: new Date('2024-03-03T00:00:00Z'), at_group_start: false })
// A lone candidate unprobed since before the range was done: its hash may still match. Kept.
await db.table('probe_candidates').insert({ id: 9001, release_id: b, kind: 'lone', message_id: '<9001@x>', file: 'x', status: 'waiting', created_at: new Date(old.getTime() - 3600_000) })
// Out of budget: one batch of two sets (a, b), the next round goes on.
assert.deepEqual(await new Par2Pruner(2).round(0), { complete: 1, judged: 0, final: 0, rows: 2 })
assert.deepEqual(await new Par2Pruner(2).round(), { complete: 0, judged: 1, final: 0, rows: 2 })
assert.equal(await count(complete), 0)
assert.equal(await count(apart), 2)
assert.equal(await count(recent), 2)
assert.equal(await count(dead), 0)
assert.equal(await count(locked), 2)
assert.equal((await db.from('par2_sets').count('* as n'))[0].n, '5')
assert.equal(await count(final), 2)
// Again: the cursor is past them, nothing more.
assert.deepEqual(await new Par2Pruner().round(), { complete: 0, judged: 0, rows: 0 })
// Probed: final. The next pass starts over from the first set.
await db.from('probe_candidates').where('id', 9001).update({ status: 'done' })
assert.deepEqual(await new Par2Pruner(2).round(), { complete: 0, judged: 0, final: 1, rows: 2 })
assert.equal(await count(final), 0)
assert.equal((await db.from('par2_sets').count('* as n'))[0].n, '6')
// A hash of its files since the merger's last window: the merger hasn't matched it yet. Kept.
const g = await release()
const unseen = await set('g', g, 1, null)
await db.from('releases').where('id', g).update({ group_id: groupId, posted_at: posted, last_posted_at: posted, first_article: 6000, last_article: 6100 })
await db.table('worker_cursors').insert({ name: 'merger', at: new Date(Date.now() - 3600_000) })
await db.table('probe_candidates').insert({ id: 9002, release_id: await release(), kind: 'lone', message_id: '<9002@x>', file: 'x', status: 'done' })
await db.table('file_hashes').insert({ candidate_id: 9002, release_id: 1, hash16k: 'g0'.padEnd(32, '0'), size: 1000 })
assert.equal((await new Par2Pruner().round()).final, 0)
// Past the hash by more than the merger's overlap.
await db.from('worker_cursors').where('name', 'merger').update({ at: new Date(Date.now() + 120_000) })
assert.equal((await new Par2Pruner().round()).final, 1)
assert.equal(await count(unseen), 0)
assert.isNotNull(range)
})
test('more sets of one created_at than a batch holds: each batch goes on past the last one', async ({ assert }) => {
test('sets of one created_at, a batch each: every one looked at', async ({ assert }) => {
// One prober flush gives thousands of sets the same created_at (to the microsecond).
const flush = new Date('2026-10-15T14:38:39.883Z')
let candidate = 7000
@@ -88,6 +118,7 @@ test.group('Par2Pruner', (group) => {
const round = await new Par2Pruner(1).round()
assert.equal(round.complete, 3)
assert.equal((await db.from('par2_files').whereIn('set_id', ['p'.repeat(32), 'q'.repeat(32), 'r'.repeat(32)]).count('* as n'))[0].n, '0')
assert.equal((await db.from('worker_cursors').where('name', 'par2-prune').firstOrFail()).key, 'r'.repeat(32))
// The pass ended: the next one starts from the first set.
assert.equal((await db.from('worker_cursors').where('name', 'par2-prune').firstOrFail()).key, '')
})
})