ruvnet / ruvnet/RuView

calibrate-serve: session stuck in `finalizing` forever when duration_s expires with frames < min_frames — no API escape

Open
#1,634 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Rust
Stars
94.4k
Forks
12.5k
Avg merge
21h 27m
Merged PRs (30d)
43

Description

Summary

In calibrate-serve (wifi-densepose-cli), if a calibration session's duration_s expires while frames_recorded is below the requested min_frames, the session enters state: "finalizing" and never leaves it. /status reports finalizing with a growing eta_s indefinitely, POST /stop answers as if no session exists, and no further POST /start can run — the only recovery is restarting the process.

Environment
  • wifi-densepose CLI built from the v2051 tag (crates/wifi-densepose-cli unmodified), calibrate-serve mode, one instance per node behind a UDP fan-out
  • 5× M5Stack Atom S3 Lite (esp32-csi-node) streaming UDP CSI, HT20 tier (52 subcarriers)
Reproduction (deterministic, 5/5 instances)

Request min_frames above what the window can deliver and let duration_s expire:

POST /api/v1/calibration/start {"tier":"ht20","duration_s":300,"min_frames":2400,"room_id":"test"}

At ~3.3 valid HT20 frames/s, 300 s yields ~1000 frames < 2400. Observed on five independent instances simultaneously (same start, same wall-clock):

instance elapsed_s frames_recorded state eta_s (growing)
1 300.10 1042 finalizing 391 → …
2 300.10 996 finalizing 423 → …
3 300.09 955 finalizing 454 → …
4 300.08 1014 finalizing 410 → …
5 300.06 606 finalizing 888 → …

All five stayed in finalizing until the processes were killed. POST /stop on a stuck instance returns the no-active-session response. First hit this on 2026-08-11 with a single instance; today's run reproduced it 5/5.

(Sessions that reach min_frames finalize and persist fine — same setup banked clean baselines minutes earlier with min_frames: 240.)

Mechanism

Three layers line up (paths/lines against v2051):

  1. wifi-densepose-signal/src/ruvsense/calibration.rs:532BaselineRecorder::finalize() correctly returns Err(CalibrationError::InsufficientFrames { got, need }) when frame_count < config.min_frames.

  2. wifi-densepose-cli/src/calibrate_api.rs:605 (async fn finalize) — writes the "finalizing" snapshot before calling the fallible recorder.finalize():

let snap = session_snapshot(&sess, "finalizing", None);
status.write().await.session = Some(snap);

let baseline: BaselineCalibration = sess
    .recorder
    .finalize()
    .map_err(|e| format!("finalize failed: {e}"))?;   // <- error path returns here;
                                                      //    the snapshot is never updated again
  1. calibrate_api.rs:537 (deadline tick) — the guard is a hardcoded floor, not the session's actual target, and the error is discarded:
if Instant::now() >= sess.deadline {
    let frames = sess.recorder.frames_recorded() as usize;
    if frames >= 10 {                                  // <- should be `frames >= target_frames`
        if let Some(done) = active.take() {
            let _ = finalize(done, &output_dir, &status).await;   // <- Err discarded
        }
    } else if let Some(mut done) = active.take() {
        // not enough frames — abort honestly rather than emit a bad baseline
        ...

So with 10 <= frames < min_frames: the tick takes the finalize path, active is already take()n (hence /stop finds nothing), recorder.finalize() errors, the Err is dropped, and the last snapshot anyone can see is "finalizing". The growing eta_s is cosmetic fallout (rate estimate decays while remaining-frames stays constant).

The honest "aborted" path for the < 10 case is exactly the right behavior — it just never runs for this range.

Suggested fix

Either of these alone unsticks it; both together are cheap defense in depth:

  1. In the deadline tick, compare against the session's real target instead of 10, and route the shortfall to the existing abort path with an InsufficientFrames-style note (aborted: only N frames of M required in the time window).
  2. In finalize(), on Err write a terminal snapshot (state: "aborted", note = the error string) instead of leaving "finalizing" behind.

Happy to send a PR for either shape if useful.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with wifi-densepose-signal/src/ruvsense/calibration.rs:532 and wifi-densepose-cli/src/calibrate_api.rs at the deadline tick and async finalize function. Reproduce with min_frames above what duration_s can deliver, then verify insufficient sessions reach a terminal aborted state with an explanatory note, /stop behaves consistently, and a later /start is possible.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.