Skip to content

Commit 413fcab

Browse files
committed
Merge #715
715: Fix multiple issues with POSIX AIO r=asomers a=asomers Fixed error handling in `AioCb::fsync`, `AioCb::read`, and `AioCb::write` Previously, the `AioCb`'s `in_progress` field would erroneously be set to `true`, even if the syscall had an error Fixes #714 AioCb::Drop will now panic for in-progress AioCb Printing a warning message to stderr isn't really appropriate, because there's no way to guarantee that stderr is even valid. Nor is aio_suspend necessarily an appropriate action to take.
2 parents 219fcd7 + bd2cda1 commit 413fcab

File tree

5 files changed

+109
-54
lines changed

5 files changed

+109
-54
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
5151
([#744](https://github.com/nix-rust/nix/pull/744))
5252
- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly.
5353
([#749](https://github.com/nix-rust/nix/pull/749))
54+
- `AioCb::Drop` will now panic if the `AioCb` is still in-progress ([#715](https://github.com/nix-rust/nix/pull/715))
5455

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

5963
# Removed
6064
- The syscall module has been removed. This only exposed enough functionality for

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ nix-test = { path = "nix-test", version = "0.0.1" }
3131
name = "test"
3232
path = "test/test.rs"
3333

34+
[[test]]
35+
name = "test-aio-drop"
36+
path = "test/sys/test_aio_drop.rs"
37+
3438
[[test]]
3539
name = "test-mount"
3640
path = "test/test_mount.rs"

src/sys/aio.rs

+17-26
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ use libc::{c_void, off_t, size_t};
44
use libc;
55
use std::fmt;
66
use std::fmt::Debug;
7-
use std::io::Write;
8-
use std::io::stderr;
97
use std::marker::PhantomData;
108
use std::mem;
119
use std::rc::Rc;
@@ -234,16 +232,22 @@ impl<'a> AioCb<'a> {
234232
/// An asynchronous version of `fsync`.
235233
pub fn fsync(&mut self, mode: AioFsyncMode) -> Result<()> {
236234
let p: *mut libc::aiocb = &mut self.aiocb;
237-
self.in_progress = true;
238-
Errno::result(unsafe { libc::aio_fsync(mode as libc::c_int, p) }).map(drop)
235+
Errno::result(unsafe {
236+
libc::aio_fsync(mode as libc::c_int, p)
237+
}).map(|_| {
238+
self.in_progress = true;
239+
})
239240
}
240241

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

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

266273
}
@@ -332,24 +339,8 @@ impl<'a> Debug for AioCb<'a> {
332339

333340
impl<'a> Drop for AioCb<'a> {
334341
/// If the `AioCb` has no remaining state in the kernel, just drop it.
335-
/// Otherwise, collect its error and return values, so as not to leak
336-
/// resources.
342+
/// Otherwise, dropping constitutes a resource leak, which is an error
337343
fn drop(&mut self) {
338-
if self.in_progress {
339-
// Well-written programs should never get here. They should always
340-
// wait for an AioCb to complete before dropping it
341-
let _ = write!(stderr(), "WARNING: dropped an in-progress AioCb");
342-
loop {
343-
let ret = aio_suspend(&[&self], None);
344-
match ret {
345-
Ok(()) => break,
346-
Err(Error::Sys(Errno::EINVAL)) => panic!(
347-
"Inconsistent AioCb.in_progress value"),
348-
Err(Error::Sys(Errno::EINTR)) => (), // Retry interrupted syscall
349-
_ => panic!("Unexpected aio_suspend return value {:?}", ret)
350-
};
351-
}
352-
let _ = self.aio_return();
353-
}
344+
assert!(!self.in_progress, "Dropped an in-progress AioCb");
354345
}
355346
}

test/sys/test_aio.rs

+57-28
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,26 @@ fn test_fsync() {
8888
aiocb.aio_return().unwrap();
8989
}
9090

91+
/// `AioCb::fsync` should not modify the `AioCb` object if libc::aio_fsync returns
92+
/// an error
93+
// Skip on Linux, because Linux's AIO implementation can't detect errors
94+
// synchronously
95+
#[test]
96+
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
97+
fn test_fsync_error() {
98+
use std::mem;
99+
100+
const INITIAL: &'static [u8] = b"abcdef123456";
101+
// Create an invalid AioFsyncMode
102+
let mode = unsafe { mem::transmute(666) };
103+
let mut f = tempfile().unwrap();
104+
f.write(INITIAL).unwrap();
105+
let mut aiocb = AioCb::from_fd( f.as_raw_fd(),
106+
0, //priority
107+
SigevNotify::SigevNone);
108+
let err = aiocb.fsync(mode);
109+
assert!(err.is_err());
110+
}
91111

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

179+
/// `AioCb::read` should not modify the `AioCb` object if libc::aio_read returns
180+
/// an error
181+
// Skip on Linux, because Linux's AIO implementation can't detect errors
182+
// synchronously
183+
#[test]
184+
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
185+
fn test_read_error() {
186+
const INITIAL: &'static [u8] = b"abcdef123456";
187+
let rbuf = Rc::new(vec![0; 4].into_boxed_slice());
188+
let mut f = tempfile().unwrap();
189+
f.write(INITIAL).unwrap();
190+
let mut aiocb = AioCb::from_boxed_slice( f.as_raw_fd(),
191+
-1, //an invalid offset
192+
rbuf.clone(),
193+
0, //priority
194+
SigevNotify::SigevNone,
195+
LioOpcode::LIO_NOP);
196+
assert!(aiocb.read().is_err());
197+
}
198+
159199
// Tests from_mut_slice
160200
#[test]
161201
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
@@ -230,6 +270,23 @@ fn test_write() {
230270
assert!(rbuf == EXPECT);
231271
}
232272

273+
/// `AioCb::write` should not modify the `AioCb` object if libc::aio_write returns
274+
/// an error
275+
// Skip on Linux, because Linux's AIO implementation can't detect errors
276+
// synchronously
277+
#[test]
278+
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
279+
fn test_write_error() {
280+
let wbuf = "CDEF".to_string().into_bytes();
281+
let mut aiocb = AioCb::from_slice( 666, // An invalid file descriptor
282+
0, //offset
283+
&wbuf,
284+
0, //priority
285+
SigevNotify::SigevNone,
286+
LioOpcode::LIO_NOP);
287+
assert!(aiocb.write().is_err());
288+
}
289+
233290
lazy_static! {
234291
pub static ref SIGNALED: AtomicBool = AtomicBool::new(false);
235292
}
@@ -442,31 +499,3 @@ fn test_lio_listio_read_immutable() {
442499
LioOpcode::LIO_READ);
443500
let _ = lio_listio(LioMode::LIO_NOWAIT, &[&mut rcb], SigevNotify::SigevNone);
444501
}
445-
446-
// Test dropping an AioCb that hasn't yet finished. Behind the scenes, the
447-
// library should wait for the AioCb's completion.
448-
#[test]
449-
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
450-
fn test_drop() {
451-
const INITIAL: &'static [u8] = b"abcdef123456";
452-
const WBUF: &'static [u8] = b"CDEF"; //"CDEF".to_string().into_bytes();
453-
let mut rbuf = Vec::new();
454-
const EXPECT: &'static [u8] = b"abCDEF123456";
455-
456-
let mut f = tempfile().unwrap();
457-
f.write(INITIAL).unwrap();
458-
{
459-
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
460-
2, //offset
461-
&WBUF,
462-
0, //priority
463-
SigevNotify::SigevNone,
464-
LioOpcode::LIO_NOP);
465-
aiocb.write().unwrap();
466-
}
467-
468-
f.seek(SeekFrom::Start(0)).unwrap();
469-
let len = f.read_to_end(&mut rbuf).unwrap();
470-
assert!(len == EXPECT.len());
471-
assert!(rbuf == EXPECT);
472-
}

test/sys/test_aio_drop.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
extern crate nix;
2+
extern crate tempfile;
3+
4+
use nix::sys::aio::*;
5+
use nix::sys::signal::*;
6+
use std::os::unix::io::AsRawFd;
7+
use tempfile::tempfile;
8+
9+
// Test dropping an AioCb that hasn't yet finished.
10+
// This must happen in its own process, because on OSX this test seems to hose
11+
// the AIO subsystem and causes subsequent tests to fail
12+
#[test]
13+
#[should_panic(expected = "Dropped an in-progress AioCb")]
14+
#[cfg(not(target_env = "musl"))]
15+
fn test_drop() {
16+
const WBUF: &'static [u8] = b"CDEF";
17+
18+
let f = tempfile().unwrap();
19+
f.set_len(6).unwrap();
20+
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
21+
2, //offset
22+
&WBUF,
23+
0, //priority
24+
SigevNotify::SigevNone,
25+
LioOpcode::LIO_NOP);
26+
aiocb.write().unwrap();
27+
}

0 commit comments

Comments
 (0)