tokio-rs/tokio · error · io::Error

not a pipe

Error message

not a pipe

What it means

OpenOptions::open opens the path then, when unchecked is false, calls is_pipe on the resulting fd; if it isn't a FIFO, returns InvalidInput 'not a pipe'. The check exists because tokio's pipe sender/receiver require an actual FIFO — a regular file, socket, or character device will not behave correctly with non-blocking I/O.

Source

Thrown at tokio/src/net/unix/pipe.rs:275

        Sender::from_file_unchecked(file)
    }

    fn open(&self, path: &Path, pipe_end: PipeEnd) -> io::Result<File> {
        let mut options = std::fs::OpenOptions::new();
        options
            .read(pipe_end == PipeEnd::Receiver)
            .write(pipe_end == PipeEnd::Sender)
            .custom_flags(libc::O_NONBLOCK);

        #[cfg(any(target_os = "linux", target_os = "android"))]
        if self.read_write {
            options.read(true).write(true);
        }

        let file = options.open(path)?;

        if !self.unchecked && !is_pipe(file.as_fd())? {
            return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a pipe"));
        }

        Ok(file)
    }
}

impl Default for OpenOptions {
    fn default() -> OpenOptions {
        OpenOptions::new()
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum PipeEnd {
    Sender,
    Receiver,
}

View on GitHub (pinned to 625954f365)

Solutions

  1. Ensure the path was created with mkfifo(1) / nix::unistd::mkfifo before opening.
  2. If you genuinely have a pipe-like fd that fails the stat check, set .unchecked(true) on the OpenOptions — but only if you understand the risk.
  3. Verify S_IFIFO via stat before opening and produce a clearer error.
  4. Check the path for typos and that the FIFO still exists (it may have been removed).

Example fix

// before
let s = OpenOptions::new().open_sender("/tmp/log.txt")?; // regular file

// after
use nix::unistd::{mkfifo, Mode};
let _ = mkfifo("/tmp/log.fifo", Mode::S_IRWXU);
let s = OpenOptions::new().open_sender("/tmp/log.fifo")?;
Defensive patterns

Strategy: validation

Validate before calling

use std::os::unix::fs::FileTypeExt;
let meta = std::fs::metadata(path)?;
if !meta.file_type().is_fifo() {
    return Err(anyhow::anyhow!("{path:?} is not a FIFO"));
}
// then:
OpenOptions::new().open_sender(path)?;

Type guard

fn is_not_a_pipe(e: &io::Error) -> bool {
    e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("not a pipe")
}

Try / catch

match OpenOptions::new().open_sender(path) {
    Ok(s) => Ok(s),
    Err(e) if e.kind() == io::ErrorKind::InvalidInput => {
        Err(anyhow::anyhow!("{path:?} is not a FIFO; run mkfifo first"))
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: Calling OpenOptions::open_sender/open_receiver (or the internal open) on a path that does not resolve to a FIFO special file.unchecked=true bypasses the check.

Common situations: Pointing tokio::net::unix::pipe at a regular file or directory by mistake; passing a Unix-domain socket path; the FIFO hasn't been created yet (mkfifo not run); wrong path/typo.

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/05860ba060246076. Report an issue: GitHub.