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
- Ensure the path was created with mkfifo(1) / nix::unistd::mkfifo before opening.
- 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.
- Verify S_IFIFO via stat before opening and produce a clearer error.
- 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
- Create the FIFO with mkfifo(1) or nix::unistd::mkfifo before opening.
- Stat the path and assert S_IFIFO before handing it to OpenOptions.
- Reserve .unchecked(true) for advanced cases where you accept the risk.
- Verify the path still exists — FIFOs can be removed out of band.
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
- not in O_WRONLY or O_RDWR access mode
- not in O_RDONLY or O_RDWR access mode
- listen cannot be called on a datagram socket
- connect cannot be called on a datagram socket
- datagram cannot be called on a stream socket
AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11).
Data as JSON: /api/errors/05860ba060246076.
Report an issue: GitHub.