| // vim: tw=80 |
| |
| // Annoyingly, Cargo is unable to conditionally build an entire test binary. So |
| // we must disable the test here rather than in Cargo.toml |
| #![cfg(target_os = "freebsd")] |
| |
| use nix::Error; |
| use nix::errno::*; |
| use nix::libc::off_t; |
| use nix::sys::aio::*; |
| use nix::sys::signal::SigevNotify; |
| use nix::unistd::{SysconfVar, sysconf}; |
| use std::os::unix::io::AsRawFd; |
| use std::{thread, time}; |
| use sysctl::CtlValue; |
| use tempfile::tempfile; |
| |
| const BYTES_PER_OP: usize = 512; |
| |
| /// Attempt to collect final status for all of `liocb`'s operations, freeing |
| /// system resources |
| fn finish_liocb(liocb: &mut LioCb) { |
| for j in 0..liocb.aiocbs.len() { |
| loop { |
| let e = liocb.error(j); |
| match e { |
| Ok(()) => break, |
| Err(Error::Sys(Errno::EINPROGRESS)) => |
| thread::sleep(time::Duration::from_millis(10)), |
| Err(x) => panic!("aio_error({:?})", x) |
| } |
| } |
| assert_eq!(liocb.aio_return(j).unwrap(), BYTES_PER_OP as isize); |
| } |
| } |
| |
| // Deliberately exceed system resource limits, causing lio_listio to return EIO. |
| // This test must run in its own process since it deliberately uses all AIO |
| // resources. ATM it is only enabled on FreeBSD, because I don't know how to |
| // check system AIO limits on other operating systems. |
| #[test] |
| fn test_lio_listio_resubmit() { |
| let mut resubmit_count = 0; |
| |
| // Lookup system resource limits |
| let alm = sysconf(SysconfVar::AIO_LISTIO_MAX) |
| .expect("sysconf").unwrap() as usize; |
| let maqpp = if let CtlValue::Int(x) = sysctl::value( |
| "vfs.aio.max_aio_queue_per_proc").unwrap(){ |
| x as usize |
| } else { |
| panic!("unknown sysctl"); |
| }; |
| |
| // Find lio_listio sizes that satisfy the AIO_LISTIO_MAX constraint and also |
| // result in a final lio_listio call that can only partially be queued |
| let target_ops = maqpp + alm / 2; |
| let num_listios = (target_ops + alm - 3) / (alm - 2); |
| let ops_per_listio = (target_ops + num_listios - 1) / num_listios; |
| assert!((num_listios - 1) * ops_per_listio < maqpp, |
| "the last lio_listio won't make any progress; fix the algorithm"); |
| println!("Using {:?} LioCbs of {:?} operations apiece", num_listios, |
| ops_per_listio); |
| |
| let f = tempfile().unwrap(); |
| let buffer_set = (0..num_listios).map(|_| { |
| (0..ops_per_listio).map(|_| { |
| vec![0u8; BYTES_PER_OP] |
| }).collect::<Vec<_>>() |
| }).collect::<Vec<_>>(); |
| |
| let mut liocbs = (0..num_listios).map(|i| { |
| let mut liocb = LioCb::with_capacity(ops_per_listio); |
| for j in 0..ops_per_listio { |
| let offset = (BYTES_PER_OP * (i * ops_per_listio + j)) as off_t; |
| let wcb = AioCb::from_slice( f.as_raw_fd(), |
| offset, |
| &buffer_set[i][j][..], |
| 0, //priority |
| SigevNotify::SigevNone, |
| LioOpcode::LIO_WRITE); |
| liocb.aiocbs.push(wcb); |
| } |
| let mut err = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone); |
| while err == Err(Error::Sys(Errno::EIO)) || |
| err == Err(Error::Sys(Errno::EAGAIN)) || |
| err == Err(Error::Sys(Errno::EINTR)) { |
| // |
| thread::sleep(time::Duration::from_millis(10)); |
| resubmit_count += 1; |
| err = liocb.listio_resubmit(LioMode::LIO_NOWAIT, |
| SigevNotify::SigevNone); |
| } |
| liocb |
| }).collect::<Vec<_>>(); |
| |
| // Ensure that every AioCb completed |
| for liocb in liocbs.iter_mut() { |
| finish_liocb(liocb); |
| } |
| |
| if resubmit_count > 0 { |
| println!("Resubmitted {:?} times, test passed", resubmit_count); |
| } else { |
| println!("Never resubmitted. Test ambiguous"); |
| } |
| } |