The range stream polls the corpus as well as the spool while it waits
Test and deploy / test (push) Successful in 56s
Test and deploy / edge (push) Successful in 2m13s
Test and deploy / deploy (push) Successful in 4m11s

The corpus fill claims chunks of a leased range ahead of the leased run (the
prefetcher's claim keys are per range and chunk), stores them in the corpus
only, and the run leaves them there. The stream looked at the corpus once per
chunk and then polled the spool only: when the store came seconds after that
look, the stream waited its whole hour and the worker lost the group and its
open batch (three groups on 2026-10-27, LOG.md (118)). Now the wait loop checks
both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Fable 5.1 committed 2026-10-27 09:19:16 +01:00
1 parent 4af5e06400
commit 31fc204c76
3 files changed
+54 -11

No files matched your search

+32
View File
@@ -153,6 +153,38 @@ test.group('Node API', (group) => {
}
})
test('a chunk the corpus fill stores while the stream waits for it comes from the corpus', async ({ client, assert }) => {
const corpus = await mkdtemp(join(tmpdir(), 'corpus-'))
const saved = coordinatorConfig.corpus.dir
coordinatorConfig.corpus.dir = corpus
try {
const [g] = await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('id')
// A capture range not in the corpus: its first chunk spooled by the leased run, the second claimed by
// the corpus fill, which stores it in the corpus only, after the stream started.
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: 1, end_id: 200, next_id: 1, capture: true }).returning('id')
const rangeId = Number(r.id)
await Spool.write(rangeId, { from: 1, to: 100 }, chunk(11))
const node = await Node.create({ name: 'n1' })
const token = (await Node.accessTokens.create(node)).value!.release()
await db.from('ranges').where('id', rangeId).update({ status: 'leased', node_id: node.id, leased_at: new Date(), heartbeat_at: new Date() })
const pending = client.get(`/api/v1/node/ranges/${rangeId}/stream`).bearerToken(token)
setTimeout(() => Corpus.write({ group: GROUP, start: 1, end: 200 }, { from: 101, to: 200 }, chunk(111), 2), 800)
const stream = await pending
stream.assertStatus(200)
const raw = stream.body() as unknown
const body: Buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(stream.text(), 'binary')
const frames = []
for await (const frame of decodeFrames([body])) frames.push(frame)
assert.deepEqual(frames.map((f) => [f.from, f.to]), [[1, 100], [101, 200]])
assert.equal(parseOverviews(unzipSync(frames[1].data).toString('utf8')).length, 3)
assert.isFalse(await Spool.has(rangeId, { from: 101, to: 200 }))
} finally {
coordinatorConfig.corpus.dir = saved
await rm(corpus, { recursive: true, force: true })
}
})
test('a chunk that does not inflate: reported, removed from the spool, the range goes back', async ({ client, assert }) => {
const [g] = await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('id')
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: 1, end_id: 300, next_id: 1, spooled_chunks: 3 }).returning('id')