8000 fix(engine): measure flush deadlines on the wall clock, not a paused tokio clock by xerj-org · Pull Request #2167 · xerj-org/xerj · GitHub
[go: up one dir, main page]

Skip to content

fix(engine): measure flush deadlines on the wall clock, not a paused tokio clock - #2167

Merged
xerj-org merged 2 commits into
mainfrom
fix/flush-deadline-wall-clock
Oct 22, 2026
Merged

xerj-org merged 2 commits into
mainfrom
fix/flush-deadline-wall-clock

Conversation

@xerj-org
Copy link
Copy Markdown
Owner

Root cause

#2129 (Fixes #1635) bounded the flush admission waits (the flush semaphore and the process-global finalize gate) and the spawned flush-task wait with tokio::time::timeout. That timer runs on tokio's clock, not the wall clock.

The engine tests a_background_merge_tick_cannot_move_a_hand_driven_fixture_segment_set and idle_index_schedules_no_merge_evaluations_without_segment_changes run under #[tokio::test(start_paused = true)]. A paused runtime that goes idle auto-advances straight to its next timer.

The finalize gate is shared across the whole process. At default test parallelism, another test (on another runtime) is often holding a permit. A paused-clock flush waiting for that permit is idle on its own runtime, so tokio jumped 120 s ahead at once and the flush failed with:

index [tick-guard] flush timed out after 120s waiting for the finalize gate; the memtable was not frozen, so this flush can be retried

The flush itself was healthy. This is a regression from #2129, not timing noise:

Production uses a real clock, so its deadlines were already correct. Only paused-clock runtimes were affected.

Fix

  • A new helper, within_wall_clock, measures both deadlines (admission and flush task) with std::time::Instant.
  • A tokio timer never fires before the wall-clock deadline on a running clock, so in production this is the same tokio::time::timeout.
  • If the timer fires early, the clock is paused. The helper then waits a real 5 ms slice on the blocking pool (no timer, so no auto-advance) and checks again. The future is polled first each time, so a permit that arrives is taken at once.
  • The error messages and the detach-on-timeout behaviour (Flush publication guard spans the whole finalize — capture-retry latency under flush load (follow-up to #1013 / #1014) #1015) are unchanged.

The tests are not weakened. Three new paused-clock tests:

  • a_paused_clock_does_not_expire_a_flush_admission_wait: a permit released after 200 ms of real time is acquired within the 120 s budget. On main this fails instantly.
  • a_paused_clock_still_enforces_the_wall_clock_flush_deadlines: the 100 ms admission deadline and the 100 ms flush-task deadline still fire on the wall clock (elapsed ≥ 100 ms). A flush task that really finishes in 150 ms passes.
  • a_paused_clock_flush_waits_for_a_finalize_gate_held_elsewhere: the CI failure end to end. A paused-clock index flushes while another runtime holds every finalize-gate permit for 200 ms.

Validation

  • cargo fmt --check passes.
  • With the fix, the full xerj-engine lib suite at default parallelism passed 30/30 runs. On main the same run failed 20/20.
  • The merge-publication module, the three new tests and a_flush_that_never_finishes_fails_that_index_and_the_run_continues ran together 100 times with 0 failures.
  • Full local CI (workspace clippy -D warnings and ci-test workspace tests): results will follow in a comment.

This unblocks Build + Test on main and on #2141.

…tokio clock

#2129 bounded the flush admission and flush-task waits with
tokio::time::timeout. Under #[tokio::test(start_paused = true)] the runtime
auto-advances to its next timer when idle, so a flush waiting on the
process-global finalize gate (held by another test's runtime) hit the 120s
deadline instantly and failed with 'timed out after 120s waiting for the
finalize gate'. Measure both deadlines with std::time::Instant and, when the
tokio timer fires early, wait a real slice on the blocking pool.
@cla-bot cla-bot Bot added the cla-signed label Oct 22, 2026
@xerj-org
Copy link
Copy Markdown
Owner Author

Xerj Merger made the fix: flush deadlines are now measured on the wall clock (within_wall_clock). Before, a paused tokio clock auto-advanced past the #2129 admission and flush-task timeouts while a flush waited on the process-global finalize gate. Root cause, bisect and new tests are in the description.

Local CI on ec63d892:

  • cargo fmt --check: OK.
  • cargo clippy --workspace --all-targets -D warnings: OK.
  • cargo test --profile ci-test --workspace --no-fail-fast: TEST_OK, 281 suites. One test was skipped: xerj-hub systemd_reads_the_units_as_a_six_hourly_oneshot. It cannot run on this box (systemd-analyze reports "Failed to create directory '/run/systemd/': Permission denied") and it is unrelated to this change.
  • xerj-engine lib suite at default parallelism (CI's mode): 30/30 runs passed. On main the same run failed 20/20.
  • Focused merge, paused-clock and finalize-gate tests: 100/100 runs passed.

On GitHub, every check that has finished passed; Build + Test was still queued.

@xerj-org
xerj-org merged commit e495614 into main Oct 22, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: autoindex at default workers stalls at 70.9% and the node stops answering searches (13,765 files / 1,153 MB)

1 participant

0