astrid-runtime/astrid · warning

Windows named-pipe endpoint is busy

Error message

Windows named-pipe endpoint is busy

What it means

ERROR_PIPE_BUSY from CreateFile means the pipe exists but all its instances currently have a waiting client or are occupied — no free instance to serve a new connection. The library maps it to io::ErrorKind::WouldBlock, signaling a transient condition the caller should retry (possibly after the server calls CreateNamedPipe again for a new instance).

Solutions

  1. Retry the connect on WouldBlock with short backoff — this is the intended handling for ERROR_PIPE_BUSY.
  2. Increase server concurrency: accept promptly and recreate pipe instances, or use WaitNamedPipe to wait for a free instance before connecting.
  3. Reduce client concurrency or pool connections to the pipe.

Example fix

// before
let stream = transport.connect(&path)?;
// after
let stream = loop {
    match transport.connect(&path) {
        Ok(s) => break s,
        Err(e) if e.kind() == io::ErrorKind::WouldBlock => { std::thread::sleep(BACKOFF); }
        Err(e) => return Err(e),
    }
};
Defensive patterns

Strategy: retry

Try / catch

match transport.connect(&path) {
    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
        // all instances busy: exponential backoff, optionally WaitNamedPipe first
        std::thread::sleep(BACKOFF);
        retry_connect(&path)
    }
    other => other,
}

Prevention

When it happens

Trigger: Multiple clients connecting concurrently to a single-instance pipe server; server accept loop busy so no instance is listening; client already queued for one instance while opening another.

Common situations: High-concurrency IPC where the server created too few pipe instances; slow accept loop under load; thundering-herd client reconnects after a server restart.

Related errors


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

Appendix: source

Thrown at crates/astrid-core/src/local_transport/windows.rs:664

    }

    let error = io::Error::last_os_error();
    match error.raw_os_error().map(i32::cast_unsigned) {
        Some(ERROR_FILE_NOT_FOUND) => Ok(EndpointState::Absent),
        Some(ERROR_PIPE_BUSY | ERROR_SEM_TIMEOUT | ERROR_ACCESS_DENIED) => {
            Ok(EndpointState::BusyOrDenied)
        },
        _ => Err(error),
    }
}

fn classify_connect_error(error: io::Error) -> io::Error {
    match error.raw_os_error().map(i32::cast_unsigned) {
        Some(ERROR_FILE_NOT_FOUND) => io::Error::new(
            io::ErrorKind::NotFound,
            "Windows named-pipe endpoint is absent",
        ),
        Some(ERROR_PIPE_BUSY) => io::Error::new(
            io::ErrorKind::WouldBlock,
            "Windows named-pipe endpoint is busy",
        ),
        Some(ERROR_ACCESS_DENIED) => io::Error::new(
            io::ErrorKind::PermissionDenied,
            "Windows named-pipe endpoint denied access",
        ),
        _ => error,
    }
}

struct PipeSecurity {
    _descriptor: LocalAllocation,
    attributes: SECURITY_ATTRIBUTES,
}

impl PipeSecurity {
    fn for_current_user() -> io::Result<Self> {

View on GitHub (pinned to affd8760f4)