use std::num::NonZeroU32;
use std::ops::ControlFlow;
use std::os::fd::AsFd;
use std::time::Duration;
use rustix::io::{Errno, write};
use crate::futures::Backoff;
pub trait WriteAllExt: AsFd {
fn write_all_retrying(&self, buf: &[u8], timeout: Duration) -> Result<(), Errno> {
if buf.is_empty() {
return Ok(());
}
let fd = self.as_fd();
let mut buf = buf;
let backoff = Backoff::Exponential {
initial: Duration::from_millis(1),
max: Duration::from_millis(50),
factor: NonZeroU32::new(2).unwrap(),
};
let done = backoff.retry_blocking(
|| loop {
match write(fd, buf) {
Ok(0) => return ControlFlow::Break(Err(Errno::IO)),
Ok(n) => {
buf = &buf[n..];
if buf.is_empty() {
return ControlFlow::Break(Ok(()));
}
}
Err(Errno::INTR) => {}
Err(Errno::AGAIN) => return ControlFlow::Continue(()),
Err(e) => return ControlFlow::Break(Err(e)),
}
},
timeout,
);
done.unwrap_or(Err(Errno::TIMEDOUT))
}
}
impl<Fd: AsFd + ?Sized> WriteAllExt for Fd {}
#[cfg(test)]
mod tests {
use std::io::{Read, pipe};
use std::thread;
use rstest::rstest;
use rustix::io::ioctl_fionbio;
use super::*;
const BIG: usize = 512 * 1024;
#[rstest]
#[case::single_write(4 * 1024)]
#[case::short_write_loop(BIG)]
fn writes_everything(#[case] size: usize) {
let (mut reader, writer) = pipe().unwrap();
let collector = thread::spawn(move || {
let mut got = Vec::new();
reader.read_to_end(&mut got).unwrap();
got
});
let data = vec![0xACu8; size];
writer.write_all_retrying(&data, Duration::from_secs(10)).unwrap();
drop(writer);
assert_eq!(collector.join().unwrap(), data);
}
#[rstest]
fn times_out_when_the_fd_never_drains() {
let (_reader, writer) = pipe().unwrap();
ioctl_fionbio(&writer, true).unwrap();
let err =
writer.write_all_retrying(&vec![0u8; BIG], Duration::from_millis(50)).unwrap_err();
assert_eq!(err, Errno::TIMEDOUT);
}
}