You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There is a thing called "packet mode" pipes in Linux, see pipe(2).
TL;TR: when opened with O_DIRECT, each write is a packet (not larger than 4096 -- PIPE_BUF).
Each read reads one "packet", if buffer is too small remain bytes are discarded.
Here is a small tool that runs dd(1) in a "packet" mode.
use std::process::Stdio;use tokio::io::AsyncReadExt;use tokio::process::Command;constREAD_BLOCK_SIZE:usize = 65536;constBYTES_TO_WRITE:usize = 65536*2;#[tokio::main]asyncfnmain(){let process = Command::new("/bin/dd").arg("if=/dev/zero")// important: sets `fcntl` F_SETFL O_DIRECT// enables so-called "packet mode", see `pipe(2)` `O_DIRECT` option.arg("oflag=direct").arg(format!("bs={}",BYTES_TO_WRITE)).arg("count=1").stdout(Stdio::piped()).spawn().unwrap();letmut stdout = process.stdout.unwrap();letmut buffer = [0u8;READ_BLOCK_SIZE];letmut bytes_read = 0;loop{let i = stdout.read(&mut buffer).await.unwrap();println!("I read {}", i);
bytes_read += i;if i == 0{break;}}if bytes_read != BYTES_TO_WRITE{panic!("Wrong number of bytes read: {bytes_read}");}}
it works!!: it reads 4096 blocks till the end (just like pipe(2) suggests).
Workaround: setting buffer size to 4096 helps. It seems that Tokio waits for more data (to fill the buffer) but no more than 4096 packet might come from the "packet" pipe.
The text was updated successfully, but these errors were encountered:
Darksonn
changed the title
tokio::process freezes with "packet pipes" on Linux when buffer is too big
Short-read optimization is wrong for O_DIRECT pipes
Dec 29, 2024
// When mio is using the epoll or kqueue selector, reading a partially full
// buffer is sufficient to show that the socket buffer has been drained.
//
// This optimization does not work for level-triggered selectors such as
// windows or when poll is used.
//
// Read more:
// https://github.com/tokio-rs/tokio/issues/5866
#[cfg(all(
not(mio_unsupported_force_poll_poll),
any(
// epoll
target_os = "android",
target_os = "illumos",
target_os = "linux",
target_os = "redox",
// kqueue
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
)
))]
if0 < n && n < len {
self.registration.clear_readiness(evt);
}
Normally, a read that is shorter than the buffer size indicates that Tokio should wait for readiness before attempting to read again. This is incorrect for O_DIRECT pipes.
@Noah-Kennedy Thoughts on what we should do here? Since the flag can be changed on an existing pipe, I'm not sure that we can just cache the flag ...?
Version
Platform
(but I tried that on several different Linuxes)
Description
The problem is covered here:
https://users.rust-lang.org/t/tokio-process-freezes-with-packet-pipes-on-linux-when-buffer-is-too-big/123103
Here is a copy
There is a thing called "packet mode" pipes in Linux, see
pipe(2)
.TL;TR: when opened with
O_DIRECT
, each write is a packet (not larger than 4096 --PIPE_BUF
).Each read reads one "packet", if buffer is too small remain bytes are discarded.
Here is a small tool that runs
dd(1)
in a "packet" mode....and it gets stuck. Here is a
strace
:Now, let's try to use blocking api.
and remove
await
fromread
:it works!!: it reads 4096 blocks till the end (just like
pipe(2)
suggests).Workaround: setting buffer size to
4096
helps. It seems that Tokio waits for more data (to fill the buffer) but no more than 4096 packet might come from the "packet" pipe.The text was updated successfully, but these errors were encountered: