astrid-runtime/astrid · error

WinFsp control endpoint remained live after stop

Error message

WinFsp control endpoint remained live after stop

What it means

After the stop handshake, stop_daemon polls the control endpoint path every 25 ms until the daemon stops listening, bounded by DAEMON_STOP_TIMEOUT. If the endpoint is still present when the deadline passes, the daemon never actually released its control socket, so teardown did not complete and the library reports it rather than returning success with a still-live daemon.

Source

Thrown at crates/astrid-storage-provider-winfsp/src/win.rs:675

        stream
            .read_exact(&mut acknowledgement)
            .await
            .context("read WinFsp stop acknowledgement")?;
        if acknowledgement[0] != b'S' {
            bail!("WinFsp daemon returned an invalid stop acknowledgement");
        }
        Result::<()>::Ok(())
    };
    tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop)
        .await
        .map_err(|_| anyhow::anyhow!("WinFsp stop timed out"))??;

    let deadline = tokio::time::Instant::now()
        .checked_add(DAEMON_STOP_TIMEOUT)
        .ok_or_else(|| anyhow::anyhow!("WinFsp stop deadline overflow"))?;
    while endpoint_is_present(control_path) {
        if tokio::time::Instant::now() >= deadline {
            bail!("WinFsp control endpoint remained live after stop");
        }
        tokio::time::sleep(Duration::from_millis(25)).await;
    }
    Ok(())
}

fn initialize_winfsp() -> Result<()> {
    load_adjacent_winfsp().context("load co-installed WinFsp runtime")?;
    winfsp_wrs::init().context("initialize installed WinFsp runtime")
}

fn load_adjacent_winfsp() -> Result<()> {
    let Some(directory) = std::env::current_exe()
        .ok()
        .and_then(|path| path.parent().map(Path::to_path_buf))
    else {
        return Ok(());
    };

View on GitHub (pinned to affd8760f4)

Solutions

  1. Close all applications holding open handles on the WinFsp-mounted volume, then retry stop_daemon.
  2. Check for orphaned daemon processes from previous runs (taskkill/Stop-Process) and remove stale control endpoint files.
  3. Increase DAEMON_STOP_TIMEOUT if the workload legitimately takes longer than the current bound to drain.
  4. If the daemon is consistently wedged, capture its logs/crash dump and file/fix the hang before teardown.

Example fix

// before: stopping while apps still hold the volume open wedges the endpoint
app.write_file_on_mount();
provider.stop_daemon().await?;
// after: release handles, then stop
drop(app); // closes all handles on the mounted volume
provider.stop_daemon().await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Before stopping, check no other handles exist on the mount (best-effort, Windows)
// Close application handles and confirm the control endpoint exists:
let endpoint_live = endpoint_is_present(control_path); // if true, daemon still running and reachable

Try / catch

match provider.stop_daemon().await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("remained live after stop") => {
        close_all_volume_handles();
        tokio::time::sleep(Duration::from_secs(1)).await;
        if endpoint_is_present(control_path) { force_terminate_daemon(); }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling stop_daemon when the daemon process accepted the stop request but never exited — e.g. it is wedged in a blocking I/O loop, still holds open handles to mounted files, or the OS has not reaped the listening endpoint before DAEMON_STOP_TIMEOUT elapses.

Common situations: Open file handles from applications using the mounted filesystem prevent daemon exit; a hung FSP loop or antivirus scanning mounted files stalls shutdown; a second orphaned daemon from a previous crashed run owns the endpoint path, so the endpoint never disappears.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/051dad964730cdf4. Report an issue: GitHub.