The window worker keeps its window over a chunk that doesn't inflate
A live reply cut short (the server's compressed data ending at ".\r\n" at a read boundary; the spool keeps what Corpus.write refuses) made the worker give the group back without a snapshot, losing the window and the batch since its last post (six times in the first 40 minutes on homelab). Now the batch before the chunk goes out, the chunk is reported (fetched again, plain the second time) and the range is asked for again from that chunk (RangeOutcome::Again): the stream waits for the new copy and the window stays. Test: the coordinator hands the same range back at its resume point after a corrupt report. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f6e3316ec
commit
29e0cc8f6e
4 files changed
+34
-8
No files matched your search
@@ -536,6 +536,9 @@ fn run_range(api: &Arc<Api>, lease: LeaseJson, config: &Config, stopping: &Atomi
|
||||
/// What one range of a held group came to.
|
||||
enum RangeOutcome {
|
||||
Done,
|
||||
/// A chunk didn't inflate: reported (the coordinator fetches it again, plain the second time) after the batch
|
||||
/// before it went out, and the range is asked for again from there; the window stays.
|
||||
Again,
|
||||
/// The group is given back (a stop, a lost lease, a failure): no more ranges.
|
||||
Stop,
|
||||
}
|
||||
@@ -626,17 +629,25 @@ fn run_group_range(api: &Arc<Api>, group_id: i64, group_name: &str, range: Range
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stopped mid-range: the batch so far goes out, so the snapshot stands at a stored resume point.
|
||||
if !reached_end && corrupt.is_none() && failure.is_none() && post_error.lock().unwrap().is_none() && indexer.next() > batch_from {
|
||||
// Stopped mid-range (or at a chunk that doesn't inflate): the batch so far goes out, so the snapshot stands at
|
||||
// a stored resume point, and a chunk reported corrupt is read again from its own start.
|
||||
if !reached_end && failure.is_none() && post_error.lock().unwrap().is_none() && indexer.next() > batch_from {
|
||||
let _ = post_tx.send(indexer.take());
|
||||
}
|
||||
drop(post_tx);
|
||||
let _ = poster.join();
|
||||
let post_error = post_error.lock().unwrap().take();
|
||||
if let Some((from, to, e)) = corrupt {
|
||||
log("warn", &format!("{group_name} {}–{}: chunk {from}–{to} doesn't inflate ({e}); reported, the group goes back", range.start, range.end));
|
||||
let _ = api.corrupt(range.id, from);
|
||||
return RangeOutcome::Stop;
|
||||
// The server's compressed reply cut short (LOG.md 2026-10-11): the coordinator fetches the chunk again,
|
||||
// plain the second time, and sets the range aside after that; the window carries on either way.
|
||||
log("warn", &format!("{group_name} {}–{}: chunk {from}–{to} doesn't inflate ({e}); reported, the range is asked for again", range.start, range.end));
|
||||
if let Err(e) = api.corrupt(range.id, from) {
|
||||
log("warn", &format!("{group_name} {}–{}: reporting the chunk: {e}; the group goes back", range.start, range.end));
|
||||
return RangeOutcome::Stop;
|
||||
}
|
||||
if post_error.is_none() {
|
||||
return RangeOutcome::Again;
|
||||
}
|
||||
}
|
||||
match post_error {
|
||||
Some(ApiError::LeaseLost(m)) => {
|
||||
@@ -758,6 +769,8 @@ fn run_group(api: &Arc<Api>, lease: GroupLeaseJson, config: &Config, stopping: &
|
||||
}
|
||||
match run_group_range(api, group_id, &group_name, range, &mut indexer, config, stopping, &totals) {
|
||||
RangeOutcome::Done => ranges += 1,
|
||||
// The same range again from the reported chunk: the stream waits for its new copy.
|
||||
RangeOutcome::Again => {}
|
||||
RangeOutcome::Stop => break,
|
||||
}
|
||||
if snapshotted.elapsed() >= config.snapshot_every {
|
||||
|
||||
Reference in new issue
Block a user