fix(flatfile): drop cursor optim + guard n_entries==0

Cursor was set to oldest of read window, not oldest of returned
batch — each page-up jumped past entire ranges (F16: 600/2000 msgs).
Always bisect now; correct + simpler. Cost: +5-10 ms per page-up at
1M msgs, well below UI threshold.

Also guard state->entries NULL when total_lines>0 && n_entries==0
(every line failed _ff_maybe_index_line). Backup would segfault.

F16 tightened to received == total_msgs. Catches both stuck-on-
entries[0] and cursor-jumping regressions.
This commit is contained in:
2026-05-04 15:09:51 +03:00
parent a7da9e2add
commit 875f8f4100
2 changed files with 30 additions and 23 deletions

View File

@@ -672,21 +672,21 @@ test_F16(fail_ctx_t* ctx)
double dt = bench_now_ms() - t0;
// Pass criterion focuses on the reviewer-reported symptom: without the
// guard, the cursor lands on entries[0] within the first few iterations
// (file has 4 index entries, after ~2 page-ups the cursor drops to msg
// 0 due to the read-window-vs-returned-batch divergence), and the next
// call returns DB_RESPONSE_EMPTY immediately, capping total_received at
// ~200 messages. With the guard the bisect fallback engages and pulls
// additional batches before genuinely reaching the top, lifting total
// well above the bug-induced ceiling.
gboolean ok = (iterations >= 5)
&& (total_received >= 500)
// Two regression classes are caught here:
// 1. The original "cursor stuck on entries[0]" symptom (early EMPTY
// after 2-3 iterations, ~200 received) — capped page-up at the top.
// 2. The deeper "cursor jumping" bug — cursor was set to the oldest
// line of the read window rather than the oldest line returned to
// the caller after pagination, so each subsequent page-up skipped
// past entire ranges of messages. With both fixed, every written
// message must be reachable through sequential page-up.
gboolean ok = (total_received == total_msgs)
&& (empty_responses == 1)
&& (iterations < 200);
auto_gchar gchar* note = g_strdup_printf(
"wrote=%d received=%d iters=%d empty=%d (without guard: <=200 received in <=3 iters)",
"wrote=%d received=%d iters=%d empty=%d "
"(without fix: ~200 received in ~3 iters; cursor-jumping mode: ~600 in ~7)",
total_msgs, total_received, iterations, empty_responses);
report(ctx, "F16", ok, dt, path, note);
be->close();