Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiple issues with POSIX AIO #715

Merged
merged 2 commits into from
Sep 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#744](https://github.com/nix-rust/nix/pull/744))
- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly.
([#749](https://github.com/nix-rust/nix/pull/749))
- `AioCb::Drop` will now panic if the `AioCb` is still in-progress ([#715](https://github.com/nix-rust/nix/pull/715))

# Fixed
- Fix compilation and tests for OpenBSD targets
([#688](https://github.com/nix-rust/nix/pull/688))
- Fixed error handling in `AioCb::fsync`, `AioCb::read`, and `AioCb::write`.
It is no longer an error to drop an `AioCb` that failed to enqueue in the OS.
([#715](https://github.com/nix-rust/nix/pull/715))

# Removed
- The syscall module has been removed. This only exposed enough functionality for
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ nix-test = { path = "nix-test", version = "0.0.1" }
name = "test"
path = "test/test.rs"

[[test]]
name = "test-aio-drop"
path = "test/sys/test_aio_drop.rs"

[[test]]
name = "test-mount"
path = "test/test_mount.rs"
Expand Down
43 changes: 17 additions & 26 deletions src/sys/aio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use libc::{c_void, off_t, size_t};
use libc;
use std::fmt;
use std::fmt::Debug;
use std::io::Write;
use std::io::stderr;
use std::marker::PhantomData;
use std::mem;
use std::rc::Rc;
Expand Down Expand Up @@ -234,16 +232,22 @@ impl<'a> AioCb<'a> {
/// An asynchronous version of `fsync`.
pub fn fsync(&mut self, mode: AioFsyncMode) -> Result<()> {
let p: *mut libc::aiocb = &mut self.aiocb;
self.in_progress = true;
Errno::result(unsafe { libc::aio_fsync(mode as libc::c_int, p) }).map(drop)
Errno::result(unsafe {
libc::aio_fsync(mode as libc::c_int, p)
}).map(|_| {
self.in_progress = true;
})
}

/// Asynchronously reads from a file descriptor into a buffer
pub fn read(&mut self) -> Result<()> {
assert!(self.mutable, "Can't read into an immutable buffer");
let p: *mut libc::aiocb = &mut self.aiocb;
self.in_progress = true;
Errno::result(unsafe { libc::aio_read(p) }).map(drop)
Errno::result(unsafe {
libc::aio_read(p)
}).map(|_| {
self.in_progress = true;
})
}

/// Retrieve return status of an asynchronous operation. Should only be
Expand All @@ -259,8 +263,11 @@ impl<'a> AioCb<'a> {
/// Asynchronously writes from a buffer to a file descriptor
pub fn write(&mut self) -> Result<()> {
let p: *mut libc::aiocb = &mut self.aiocb;
self.in_progress = true;
Errno::result(unsafe { libc::aio_write(p) }).map(drop)
Errno::result(unsafe {
libc::aio_write(p)
}).map(|_| {
self.in_progress = true;
})
}

}
Expand Down Expand Up @@ -332,24 +339,8 @@ impl<'a> Debug for AioCb<'a> {

impl<'a> Drop for AioCb<'a> {
/// If the `AioCb` has no remaining state in the kernel, just drop it.
/// Otherwise, collect its error and return values, so as not to leak
/// resources.
/// Otherwise, dropping constitutes a resource leak, which is an error
fn drop(&mut self) {
if self.in_progress {
// Well-written programs should never get here. They should always
// wait for an AioCb to complete before dropping it
let _ = write!(stderr(), "WARNING: dropped an in-progress AioCb");
loop {
let ret = aio_suspend(&[&self], None);
match ret {
Ok(()) => break,
Err(Error::Sys(Errno::EINVAL)) => panic!(
"Inconsistent AioCb.in_progress value"),
Err(Error::Sys(Errno::EINTR)) => (), // Retry interrupted syscall
_ => panic!("Unexpected aio_suspend return value {:?}", ret)
};
}
let _ = self.aio_return();
}
assert!(!self.in_progress, "Dropped an in-progress AioCb");
}
}
85 changes: 57 additions & 28 deletions test/sys/test_aio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ fn test_fsync() {
aiocb.aio_return().unwrap();
}

/// `AioCb::fsync` should not modify the `AioCb` object if libc::aio_fsync returns
/// an error
// Skip on Linux, because Linux's AIO implementation can't detect errors
// synchronously
#[test]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
fn test_fsync_error() {
use std::mem;

const INITIAL: &'static [u8] = b"abcdef123456";
// Create an invalid AioFsyncMode
let mode = unsafe { mem::transmute(666) };
let mut f = tempfile().unwrap();
f.write(INITIAL).unwrap();
let mut aiocb = AioCb::from_fd( f.as_raw_fd(),
0, //priority
SigevNotify::SigevNone);
let err = aiocb.fsync(mode);
assert!(err.is_err());
}

#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
Expand Down Expand Up @@ -156,6 +176,26 @@ fn test_read() {
assert!(EXPECT == rbuf.deref().deref());
}

/// `AioCb::read` should not modify the `AioCb` object if libc::aio_read returns
/// an error
// Skip on Linux, because Linux's AIO implementation can't detect errors
// synchronously
#[test]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
fn test_read_error() {
const INITIAL: &'static [u8] = b"abcdef123456";
let rbuf = Rc::new(vec![0; 4].into_boxed_slice());
let mut f = tempfile().unwrap();
f.write(INITIAL).unwrap();
let mut aiocb = AioCb::from_boxed_slice( f.as_raw_fd(),
-1, //an invalid offset
rbuf.clone(),
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
assert!(aiocb.read().is_err());
}

// Tests from_mut_slice
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
Expand Down Expand Up @@ -230,6 +270,23 @@ fn test_write() {
assert!(rbuf == EXPECT);
}

/// `AioCb::write` should not modify the `AioCb` object if libc::aio_write returns
/// an error
// Skip on Linux, because Linux's AIO implementation can't detect errors
// synchronously
#[test]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
fn test_write_error() {
let wbuf = "CDEF".to_string().into_bytes();
let mut aiocb = AioCb::from_slice( 666, // An invalid file descriptor
0, //offset
&wbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
assert!(aiocb.write().is_err());
}

lazy_static! {
pub static ref SIGNALED: AtomicBool = AtomicBool::new(false);
}
Expand Down Expand Up @@ -442,31 +499,3 @@ fn test_lio_listio_read_immutable() {
LioOpcode::LIO_READ);
let _ = lio_listio(LioMode::LIO_NOWAIT, &[&mut rcb], SigevNotify::SigevNone);
}

// Test dropping an AioCb that hasn't yet finished. Behind the scenes, the
// library should wait for the AioCb's completion.
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_drop() {
const INITIAL: &'static [u8] = b"abcdef123456";
const WBUF: &'static [u8] = b"CDEF"; //"CDEF".to_string().into_bytes();
let mut rbuf = Vec::new();
const EXPECT: &'static [u8] = b"abCDEF123456";

let mut f = tempfile().unwrap();
f.write(INITIAL).unwrap();
{
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
&WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
}

f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert!(len == EXPECT.len());
assert!(rbuf == EXPECT);
}
27 changes: 27 additions & 0 deletions test/sys/test_aio_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
extern crate nix;
extern crate tempfile;

use nix::sys::aio::*;
use nix::sys::signal::*;
use std::os::unix::io::AsRawFd;
use tempfile::tempfile;

// Test dropping an AioCb that hasn't yet finished.
// This must happen in its own process, because on OSX this test seems to hose
// the AIO subsystem and causes subsequent tests to fail
#[test]
#[should_panic(expected = "Dropped an in-progress AioCb")]
#[cfg(not(target_env = "musl"))]
fn test_drop() {
const WBUF: &'static [u8] = b"CDEF";

let f = tempfile().unwrap();
f.set_len(6).unwrap();
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
&WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
}