par2 file lists dropped once their holders are final; late candidates claimable
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:
1 parent
4f41303482
commit
8ad5ea162c
7 files changed
+152
-61
No files matched your search
@@ -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, '')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user