astrid-runtime/astrid · error

WinFsp stop timed out

Error message

WinFsp stop timed out

What it means

Raised in `stop_daemon` (crates/astrid-storage-provider-winfsp/src/win.rs:668) when the asynchronous stop sequence — connecting to the daemon's control endpoint, sending `STOP`, and reading the single-byte acknowledgement — does not complete within `DAEMON_STOP_TIMEOUT`. The timeout wrapper converts the elapsed future into this error. It indicates the WinFsp daemon is hung, unreachable in a non-NotFound way, or its control channel is unresponsive.

Source

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

        };
        stream
            .write_all(b"STOP")
            .await
            .context("send WinFsp stop")?;
        stream.flush().await.context("flush WinFsp stop")?;
        let mut acknowledgement = [0_u8; 1];
        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")
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Check whether the WinFsp service process is still running and inspect its logs for a stuck filesystem callback blocking the service loop.
  2. Verify nothing else is holding the control endpoint connection open (a leaked client keeps the stop handshake from completing).
  3. If the daemon is wedged, forcibly terminate the service process, then remove the leftover endpoint file before remounting.
  4. Retry `stop_daemon` once the system is under lighter load; if timeouts recur, consider raising `DAEMON_STOP_TIMEOUT`.
  5. Confirm the mountpoint was actually unmounted after force-kill to avoid the stale-mount state.

Example fix

// before
tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop)
    .await
    .map_err(|_| anyhow::anyhow!("WinFsp stop timed out"))??;
// after (caller-side guard)
match tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop_daemon(&control_path)).await {
    Ok(Ok(())) => {}
    Ok(Err(e)) => warn!(error = %e, "graceful stop failed; forcing teardown"),
    Err(_) => { warn!("WinFsp stop timed out; killing daemon"); kill_daemon(&pid_path); }
}
Defensive patterns

Strategy: retry

Validate before calling

// Confirm the daemon is responsive before attempting a stop
if !endpoint_is_present(control_path) {
    return Ok(()); // already gone, no stop needed
}

Type guard

fn daemon_stoppable(control_path: &Path) -> bool {
    endpoint_is_present(control_path) && daemon_pid_is_alive(pid_path())
}

Try / catch

match tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop_daemon(&control_path)).await {
    Ok(Ok(())) => info!("daemon stopped cleanly"),
    Ok(Err(e)) => { warn!(error = %e, "stop failed; forcing teardown"); force_teardown(); }
    Err(_elapsed) => { warn!("WinFsp stop timed out; killing daemon"); force_teardown(); }
}

Prevention

When it happens

Trigger: Specifically: `tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop)` elapses. Cases: `local_transport::connect(control_path)` accepts but never completes, the `STOP` write never flushes, or the daemon never sends the `b'S'` acknowledgement byte before the deadline.

Common situations: The WinFsp daemon thread is blocked inside a filesystem callback and cannot service the control socket; the endpoint exists but the daemon process is wedged or being torn down by the OS; resource exhaustion (the daemon's runtime cannot make progress); a stale endpoint held by a dead process whose socket still accepts connections.

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/0dd2d232afa48551. Report an issue: GitHub.