#[cfg(unix)]
use std::os::fd::OwnedFd as OwnedFile;
#[cfg(windows)]
use std::os::windows::io::OwnedHandle as OwnedFile;
use std::sync::Arc;
use super::*;
fn pool_charged() -> usize {
crate::buf::with_pool(|p| p.map_or(0, |p| p.charged() - p.reserved()))
}
pub(super) async fn pends<F: Future + Unpin>(fut: &mut F) {
let polled = poll_fn(|cx| core::task::Poll::Ready(Pin::new(&mut *fut).poll(cx))).await;
assert!(polled.is_pending(), "it did not wait");
}
pub(super) fn one_blocking_thread() -> Runtime {
Builder::new()
.worker_threads(1)
.max_blocking_threads(1)
.pin_workers(false)
.build()
.unwrap()
}
pub(super) fn hold_the_pool() -> (std::sync::mpsc::Sender<()>, crate::runtime::Task<()>) {
let (hold, held) = std::sync::mpsc::channel::<()>();
let busy = crate::runtime::spawn_blocking(move || {
let _ = held.recv();
});
(hold, busy)
}
pub(super) async fn both<A: Future + Unpin, B: Future + Unpin>(
mut a: A,
mut b: B,
) -> (A::Output, B::Output) {
let (mut x, mut y) = (None, None);
poll_fn(|cx| {
if x.is_none()
&& let core::task::Poll::Ready(out) = Pin::new(&mut a).poll(cx)
{
x = Some(out);
}
if y.is_none()
&& let core::task::Poll::Ready(out) = Pin::new(&mut b).poll(cx)
{
y = Some(out);
}
if x.is_some() && y.is_some() {
core::task::Poll::Ready(())
} else {
core::task::Poll::Pending
}
})
.await;
(x.unwrap(), y.unwrap())
}
#[test]
fn a_lent_read_takes_the_file_at_the_cursor_with_no_pool_buffer() {
let rt = runtime();
let tmp = Temp::new("lent-read");
let path = tmp.file("data");
let data = pattern(3 << 20 | 777);
std::fs::write(&path, &data).unwrap();
let (got, charged) = rt.block_on(async {
within(spawn(async move {
let file = Handle::new(File::open(&path).unwrap()).unwrap();
let mut got = Vec::with_capacity(data.len() + 1);
got.push(b'>');
loop {
got.reserve(1 << 20);
let (n, back) = file.read_owned(got).await;
got = back;
if n.unwrap() == 0 {
break;
}
}
let charged = pool_charged();
assert!(got[1..] == data[..], "the bytes differ from the file's");
(got.len() - 1, charged)
}))
.await
});
assert_eq!(got, 3 << 20 | 777);
assert_eq!(charged, 0, "the bytes went through a pool buffer");
}
#[test]
fn bytes_read_ahead_go_into_a_lent_buffer_first() {
let rt = runtime();
let tmp = Temp::new("lent-ahead");
let path = tmp.file("data");
std::fs::write(&path, b"hello world").unwrap();
rt.block_on(async {
let mut file = Handle::new(File::open(&path).unwrap()).unwrap();
let mut six = [0u8; 6];
assert_eq!(within(read_some(&mut file, &mut six)).await.unwrap(), 6);
let mut buf = Vec::with_capacity(64);
buf.push(b'>');
let (n, buf) = within(file.read_owned(buf)).await;
assert_eq!((n.unwrap(), &buf[..]), (5, &b">world"[..]));
let (n, _) = within(file.read_owned(Vec::with_capacity(8))).await;
assert_eq!(n.unwrap(), 0, "the end of the file");
let full = vec![1u8; 2].into_boxed_slice().into_vec();
let (n, back) = within(file.read_owned(full)).await;
assert_eq!((n.unwrap(), &back[..]), (0, &[1u8, 1][..]));
});
}
#[test]
fn a_lent_read_sees_the_writes_before_it() {
let rt = runtime();
let tmp = Temp::new("lent-sees");
let path = tmp.file("data");
let data = pattern(1 << 20);
rt.block_on(async {
let mut file = Handle::new(read_write(&path)).unwrap();
within(write_all(&mut file, &data)).await.unwrap();
let (n, _) = within(file.write_owned(b"tail".to_vec())).await;
assert_eq!(n.unwrap(), 4);
within(seek(&mut file, SeekFrom::Start(0))).await.unwrap();
let (n, got) = within(file.read_owned(Vec::with_capacity(2 << 20))).await;
assert_eq!(n.unwrap(), data.len() + 4);
assert!(got[..data.len()] == data[..] && &got[data.len()..] == b"tail");
});
}
#[test]
fn a_dropped_lent_read_moves_the_cursor_nowhere() {
let rt = one_blocking_thread();
let tmp = Temp::new("lent-drop");
let path = tmp.file("data");
std::fs::write(&path, b"from the start").unwrap();
rt.block_on(async {
let mut file = Handle::new(File::open(&path).unwrap()).unwrap();
file.file().unwrap().keep_on_pool();
let (release, busy) = hold_the_pool();
let mut lent = Box::pin(file.read_owned(Vec::with_capacity(4)));
pends(&mut lent).await;
drop(lent);
release.send(()).unwrap();
within(busy).await;
let got = within(read_to_end(&mut file)).await.unwrap();
assert_eq!(got, b"from the start");
});
}
#[test]
fn two_lent_reads_at_once_take_consecutive_bytes() {
let rt = runtime();
let tmp = Temp::new("lent-two");
let path = tmp.file("data");
std::fs::write(&path, b"abcdefgh").unwrap();
rt.block_on(async {
let file = Handle::new(File::open(&path).unwrap()).unwrap();
let exactly = || {
let mut b = vec![0u8; 4].into_boxed_slice().into_vec();
b.clear();
b
};
let one = Box::pin(file.read_owned(exactly()));
let two = Box::pin(file.read_owned(exactly()));
let ((n1, b1), (n2, b2)) = within(both(one, two)).await;
assert_eq!((n1.unwrap(), n2.unwrap()), (4, 4));
let mut got = [b1, b2];
got.sort();
assert_eq!((&got[0][..], &got[1][..]), (&b"abcd"[..], &b"efgh"[..]));
});
}
#[test]
fn lent_writes_land_in_order_among_copied_writes() {
let rt = runtime();
let tmp = Temp::new("lent-order");
let path = tmp.file("data");
let (a, b, c) = (pattern(300_000), vec![b'b'; 200_000], vec![b'c'; 100_000]);
let mut expected = a.clone();
expected.extend_from_slice(&b);
expected.extend_from_slice(&c);
expected.extend_from_slice(b"last");
let got = rt.block_on(async {
within(spawn(async move {
let mut file = Handle::new(read_write(&path)).unwrap();
within(write_all(&mut file, &a)).await.unwrap();
let (n, back) = within(file.write_owned(b)).await;
assert_eq!(n.unwrap(), 200_000);
assert_eq!(back.len(), 200_000, "the buffer came back whole");
let mut lent = Box::pin(file.write_owned(c));
let first = poll_fn(|cx| core::task::Poll::Ready(Pin::new(&mut lent).poll(cx))).await;
within(write_all(&mut &file, b"last")).await.unwrap();
let (n, _) = match first {
core::task::Poll::Ready(done) => {
drop(lent);
done
}
core::task::Poll::Pending => within(lent).await,
};
assert_eq!(n.unwrap(), 100_000);
within(flush(&mut file)).await.unwrap();
std::fs::read(&path).unwrap()
}))
.await
});
assert!(got == expected, "the writes landed out of order");
}
#[test]
fn a_lent_write_takes_no_pool_buffer() {
let rt = runtime();
let tmp = Temp::new("lent-nopool");
let path = tmp.file("data");
let data = pattern(1 << 20);
let expected = data.clone();
let charged = rt.block_on(async {
let open = path.clone();
within(spawn(async move {
let mut file = Handle::new(read_write(&open)).unwrap();
let (n, _) = within(file.write_owned(data)).await;
assert_eq!(n.unwrap(), 1 << 20);
within(flush(&mut file)).await.unwrap();
pool_charged()
}))
.await
});
assert_eq!(charged, 0, "the bytes went through a pool buffer");
assert!(std::fs::read(&path).unwrap() == expected);
}
#[test]
fn a_lent_write_waits_for_a_write_still_queued() {
let rt = one_blocking_thread();
let tmp = Temp::new("lent-queued");
let path = tmp.file("data");
let (a, b) = (pattern(100_000), vec![b'b'; 1000]);
let mut expected = a.clone();
expected.extend_from_slice(&b);
rt.block_on(async {
let (hold, busy) = hold_the_pool();
let mut file = Handle::new(read_write(&path)).unwrap();
file.file().unwrap().keep_on_pool();
within(write_all(&mut file, &a)).await.unwrap();
let mut lent = Box::pin(file.write_owned(b));
pends(&mut lent).await;
hold.send(()).unwrap();
within(busy).await;
let (n, _) = within(lent).await;
assert_eq!(n.unwrap(), 1000);
within(flush(&mut file)).await.unwrap();
});
assert!(
std::fs::read(&path).unwrap() == expected,
"the queued write was lost"
);
}
#[test]
fn a_dropped_lent_write_that_started_still_lands() {
let rt = one_blocking_thread();
let tmp = Temp::new("lent-orphan");
let path = tmp.file("data");
let data = pattern(4 << 20);
let expected = data.clone();
rt.block_on(async {
let mut file = Handle::new(read_write(&path)).unwrap();
file.file().unwrap().keep_on_pool();
let (release, busy) = hold_the_pool();
let mut lent = Box::pin(file.write_owned(data));
pends(&mut lent).await;
drop(lent);
release.send(()).unwrap();
within(busy).await;
within(write_all(&mut file, b"after")).await.unwrap();
within(flush(&mut file)).await.unwrap();
});
let got = std::fs::read(&path).unwrap();
assert!(
got.len() == expected.len() + 5 && got[..expected.len()] == expected[..],
"{} bytes in the file",
got.len()
);
assert_eq!(&got[expected.len()..], b"after");
}
#[test]
fn a_dropped_handle_still_lands_a_started_lent_write() {
let rt = one_blocking_thread();
let tmp = Temp::new("lent-handle");
let path = tmp.file("data");
let data = pattern(4 << 20);
let expected = data.clone();
let open = path.clone();
rt.block_on(async move {
let file = Handle::new(read_write(&open)).unwrap();
file.file().unwrap().keep_on_pool();
let (release, busy) = hold_the_pool();
let mut lent = Box::pin(file.write_owned(data));
pends(&mut lent).await;
drop(lent);
drop(file);
release.send(()).unwrap();
within(busy).await;
});
let deadline = Instant::now() + Duration::from_secs(10);
let mut pause = Duration::from_micros(50);
while std::fs::read(&path).unwrap() != expected {
assert!(Instant::now() < deadline, "the write never landed");
std::thread::sleep(pause);
pause = (pause * 2).min(Duration::from_millis(20));
}
}
#[test]
fn a_lent_write_that_fails_gives_the_buffer_back_and_fails_until_a_flush() {
let rt = runtime();
let tmp = Temp::new("lent-fail");
let path = tmp.file("data");
std::fs::write(&path, b"read only").unwrap();
rt.block_on(async {
let mut file = Handle::new(File::open(&path).unwrap()).unwrap();
let (n, back) = within(file.write_owned(b"nope".to_vec())).await;
assert!(n.is_err(), "a write to a read-only file went through");
assert_eq!(back, b"nope");
let (n, _) = within(file.write_owned(b"again".to_vec())).await;
assert!(n.is_err(), "the failure is kept for the next write");
assert!(
within(flush(&mut file)).await.is_err(),
"the flush reports it"
);
let (n, _) = within(file.write_owned(Vec::new())).await;
assert_eq!(n.unwrap(), 0);
});
}
#[test]
fn a_pipe_opened_as_a_file_reads_and_writes_lent_buffers() {
let rt = runtime();
let (r, w) = std::io::pipe().unwrap();
rt.block_on(async {
let reader = Handle::new(File::from(OwnedFile::from(r))).unwrap();
let writer = Arc::new(Handle::new(File::from(OwnedFile::from(w))).unwrap());
let sender = Arc::clone(&writer);
let sent = spawn(async move { sender.write_owned(b"through the pipe".to_vec()).await });
let (n, got) = within(reader.read_owned(Vec::with_capacity(64))).await;
assert_eq!((n.unwrap(), &got[..]), (16, &b"through the pipe"[..]));
let (n, _) = within(sent).await;
assert_eq!(n.unwrap(), 16);
});
}