test: harden two recurring flaky tests (sigterm readiness + bgsave existence)#264
Conversation
Two integration tests were flaking under CI load, each costing a full ~15-min re-run multiple times this week. Both were timing/readiness flakes in test infra, not product bugs -- the SIGTERM shutdown path and the BGSAVE write path both work correctly. - tests/sigterm_shutdown.rs: wait_for_ready polled TCP readiness with a single fixed deadline and no way to tell "still starting up" apart from "already crashed" -- a real crash burned the whole 60s budget before producing an uninformative panic. wait_for_ready now takes &mut Child and checks try_wait() every poll tick, returning a Readiness enum (Ready / Exited(status) / TimedOut) so a dead child fails fast. Every readiness panic now surfaces the captured moon.stdout.log / moon.stderr.log via a new readiness_failure_message() helper, so a genuine failure stays diagnosable instead of collapsing into the same message as host-load slowness. The deadline also doubles under CI (ready_timeout(), 60s -> 120s when the CI env var is set) since the observed flake was macOS CI runner startup latency (fork+exec+bind+listen), not a hang in the SIGTERM logic. - tests/integration.rs: test_bgsave_creates_rdb_file asserted dump.rdb existence immediately after bgsave_with_retry, whose only synchronization was a fixed 200ms sleep. BGSAVE queues the write on a spawn_blocking thread and replies before the write lands on disk (src/command/ persistence.rs bgsave_start) -- under CI thread-pool contention, 200ms is a guess, not a guarantee. Added wait_for_file(path, timeout): a bounded poll (50ms interval) replacing the point-in-time assumption. Applied consistently to all three call sites that depend on the RDB file existing before either asserting on it or shutting the server down for a restart-and-restore test (test_bgsave_creates_rdb_file, test_rdb_restore_on_startup, test_aof_priority_over_rdb). The MOON-magic-bytes assertion is unchanged. Verified: both suites pass locally (sigterm_shutdown x3 runs, 7/7 each; integration test_bgsave_creates_rdb_file, test_rdb_restore_on_startup, test_aof_priority_over_rdb all green); cargo fmt --check and cargo clippy (default features, and --no-default-features --features runtime-tokio,jemalloc) both clean. author: Tin Dang <tindang.ht97@gmail.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
📝 WalkthroughWalkthroughThe changes harden test timing around SIGTERM startup readiness and background RDB creation. SIGTERM tests now detect child exits and expose logs, while persistence tests poll for completed RDB files before continuing. ChangesSIGTERM readiness diagnostics
BGSAVE persistence timing
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/sigterm_shutdown.rs (1)
222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated readiness-check-and-panic pattern into a helper.
Both call sites repeat the same five-line block: compute
ready_timeout(), callwait_for_ready, check for non-Ready, formatreadiness_failure_message, kill+wait the child, and panic. Extracting a helper eliminates the duplication and guarantees consistent cleanup behavior if a third call site is added later.♻️ Optional refactor
+fn wait_for_ready_or_panic( + child: &mut std::process::Child, + port: u16, + dir: &std::path::Path, + label: &str, +) { + let deadline = ready_timeout(); + let outcome = wait_for_ready(child, port, deadline); + if !matches!(outcome, Readiness::Ready) { + let msg = readiness_failure_message(label, port, dir, deadline, &outcome); + let _ = child.kill(); + let _ = child.wait(); + panic!("{msg}"); + } +}Then both call sites collapse to:
- let deadline = ready_timeout(); - let outcome = wait_for_ready(&mut child, port, deadline); - if !matches!(outcome, Readiness::Ready) { - let msg = readiness_failure_message(label, port, &dir, deadline, &outcome); - let _ = child.kill(); - let _ = child.wait(); - panic!("{msg}"); - } + wait_for_ready_or_panic(&mut child, port, &dir, label);and:
- let deadline = ready_timeout(); - let outcome = wait_for_ready(&mut child, port, deadline); - if !matches!(outcome, Readiness::Ready) { - let msg = readiness_failure_message("storm", port, &dir, deadline, &outcome); - let _ = child.kill(); - let _ = child.wait(); - panic!("{msg}"); - } + wait_for_ready_or_panic(&mut child, port, &dir, "storm");Also applies to: 366-372
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/sigterm_shutdown.rs` around lines 222 - 231, Extract the repeated readiness handling from both call sites into a shared helper near the existing readiness utilities. The helper should accept the child process, port, label, and directory, compute ready_timeout(), call wait_for_ready(), and on non-Ready outcomes build readiness_failure_message(), kill and wait for the child, then panic; update both call sites to use it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/sigterm_shutdown.rs`:
- Around line 222-231: Extract the repeated readiness handling from both call
sites into a shared helper near the existing readiness utilities. The helper
should accept the child process, port, label, and directory, compute
ready_timeout(), call wait_for_ready(), and on non-Ready outcomes build
readiness_failure_message(), kill and wait for the child, then panic; update
both call sites to use it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c5ec4750-b5bc-404e-9633-c363ce9291e8
📒 Files selected for processing (3)
CHANGELOG.mdtests/integration.rstests/sigterm_shutdown.rs
…271) tests/oom_bypass_closure.rs readiness had a fixed 30s connect deadline with no CI allowance and no liveness check on the spawned server: test_case_e_cross_db_copy_oom lost the 30s race on a loaded macOS CI runner and a crashed child burned the full deadline with a useless "connection refused" panic. Mirror the tests/sigterm_shutdown.rs remedy from PR #264: - wait_ready(guard, dir, port) polls child.try_wait() between bounded 1s connect windows and panics immediately with moon.stderr.log / moon.stdout.log tails if the server exited; - readiness deadline doubles to 120s under CI; - Client::connect became Client::try_connect(port, window) returning Option so the caller owns the deadline policy. All 8 tests pass locally (release-fast). author: Tin Dang <tindang.ht97@gmail.com> Co-authored-by: Tin Dang <tindang.ht97@gmail.com>
Two integration tests flake under CI load and have each cost a full ~15-min re-run multiple times this week, taxing every merge. Root-caused, not papered over:
sigterm_shutdown.rs—wait_for_readynow takes&mut Child, checkstry_wait()each 100ms tick and returnsReadiness{Ready|Exited|TimedOut}so a dead child fails fast instead of burning the whole deadline; surfacesmoon.stdout/stderr.login the panic; doublesREADY_TIMEOUT(60→120s) only whenCIis set. No post-ready assertion touched.integration.rs— new boundedwait_for_file(path, timeout)(50ms poll) replaces the immediateassert!(dump.rdb exists)after BGSAVE (a background save that replies before the file lands). Applied at all 3 affected sites (test_bgsave_creates_rdb_file,test_rdb_restore_on_startup,test_aof_priority_over_rdb). Magic-bytes assertion kept.Verified: sigterm suite 7/7 ×3, the 3 integration tests green, clippy+fmt clean both feature sets. No blind sleeps added — bounded polling only.
Summary by CodeRabbit