Skip to content

Commit 0847bbf

Browse files
committed
Fix thread safety issues in aio, chdir, and wait tests
They have four problems: * The chdir tests change the process's cwd, which is global. Protect them all with a mutex. * The wait tests will reap any subprocess, and several tests create subprocesses. Protect them all with a mutex so only one subprocess-creating test will run at a time. * When a multithreaded test forks, the child process can sometimes block in the stack unwinding code. It blocks on a mutex that was held by a different thread in the parent, but that thread doesn't exist in the child, so a deadlock results. Fix this by immediately calling _exit in the child processes. * My previous attempt at thread safety in the aio tests didn't work, because anonymous MutexGuards drop immediately. Fix this by naming the SIGUSR2_MTX MutexGuards.
1 parent 386c50c commit 0847bbf

File tree

5 files changed

+48
-23
lines changed

5 files changed

+48
-23
lines changed

test/sys/test_aio.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::io::{Write, Read, Seek, SeekFrom};
88
use std::ops::Deref;
99
use std::os::unix::io::AsRawFd;
1010
use std::rc::Rc;
11-
use std::sync::Mutex;
1211
use std::sync::atomic::{AtomicBool, Ordering};
1312
use std::{thread, time};
1413
use tempfile::tempfile;
@@ -233,8 +232,6 @@ fn test_write() {
233232

234233
lazy_static! {
235234
pub static ref SIGNALED: AtomicBool = AtomicBool::new(false);
236-
// protects access to SIGUSR2 handlers, not just SIGNALED
237-
pub static ref SIGUSR2_MTX: Mutex<()> = Mutex::new(());
238235
}
239236

240237
extern fn sigfunc(_: c_int) {
@@ -246,7 +243,7 @@ extern fn sigfunc(_: c_int) {
246243
#[test]
247244
#[cfg_attr(any(all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips"), ignore)]
248245
fn test_write_sigev_signal() {
249-
let _ = SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test");
246+
let m = ::SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test");
250247
let sa = SigAction::new(SigHandler::Handler(sigfunc),
251248
SA_RESETHAND,
252249
SigSet::empty());
@@ -279,6 +276,7 @@ fn test_write_sigev_signal() {
279276
let len = f.read_to_end(&mut rbuf).unwrap();
280277
assert!(len == EXPECT.len());
281278
assert!(rbuf == EXPECT);
279+
drop(m); // appease the unused_variable checker
282280
}
283281

284282
// Test lio_listio with LIO_WAIT, so all AIO ops should be complete by the time
@@ -376,7 +374,7 @@ fn test_lio_listio_nowait() {
376374
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
377375
#[cfg_attr(any(target_arch = "mips", target_env = "musl"), ignore)]
378376
fn test_lio_listio_signal() {
379-
let _ = SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test");
377+
let m = ::SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test");
380378
const INITIAL: &'static [u8] = b"abcdef123456";
381379
const WBUF: &'static [u8] = b"CDEF";
382380
let rbuf = Rc::new(vec![0; 4].into_boxed_slice());
@@ -422,6 +420,7 @@ fn test_lio_listio_signal() {
422420
let len = f.read_to_end(&mut rbuf2).unwrap();
423421
assert!(len == EXPECT.len());
424422
assert!(rbuf2 == EXPECT);
423+
drop(m); // appease the unused_variable checker
425424
}
426425

427426
// Try to use lio_listio to read into an immutable buffer. It should fail

test/sys/test_wait.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use libc::exit;
66

77
#[test]
88
fn test_wait_signal() {
9+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
10+
911
match fork() {
1012
Ok(Child) => pause().unwrap_or(()),
1113
Ok(Parent { child }) => {
@@ -15,10 +17,13 @@ fn test_wait_signal() {
1517
// panic, fork should never fail unless there is a serious problem with the OS
1618
Err(_) => panic!("Error: Fork Failed")
1719
}
20+
drop(m); // appease the unused_variable checker
1821
}
1922

2023
#[test]
2124
fn test_wait_exit() {
25+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
26+
2227
match fork() {
2328
Ok(Child) => unsafe { exit(12); },
2429
Ok(Parent { child }) => {
@@ -27,4 +32,5 @@ fn test_wait_exit() {
2732
// panic, fork should never fail unless there is a serious problem with the OS
2833
Err(_) => panic!("Error: Fork Failed")
2934
}
35+
drop(m); // appease the unused_variable checker
3036
}

test/test.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod test_unistd;
2525

2626
use nixtest::assert_size_of;
2727
use std::os::unix::io::RawFd;
28+
use std::sync::Mutex;
2829
use nix::unistd::read;
2930

3031
/// Helper function analogous to std::io::Read::read_exact, but for `RawFD`s
@@ -37,6 +38,17 @@ fn read_exact(f: RawFd, buf: &mut [u8]) {
3738
}
3839
}
3940

41+
lazy_static! {
42+
/// Any test that changes the process's current working directory must grab
43+
/// this mutex
44+
pub static ref CWD_MTX: Mutex<()> = Mutex::new(());
45+
/// Any test that creates child processes must grab this mutex, regardless
46+
/// of what it does with those children.
47+
pub static ref FORK_MTX: Mutex<()> = Mutex::new(());
48+
/// Any test that registers a SIGUSR2 handler must grab this mutex
49+
pub static ref SIGUSR2_MTX: Mutex<()> = Mutex::new(());
50+
}
51+
4052
#[test]
4153
pub fn test_size_of_long() {
4254
// This test is mostly here to ensure that 32bit CI is correctly

test/test_mq.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use nix::Error::Sys;
1616

1717
#[test]
1818
fn test_mq_send_and_receive() {
19+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
1920

2021
const MSG_SIZE: c_long = 32;
2122
let attr = MqAttr::new(0, 10, MSG_SIZE, 0);
@@ -53,6 +54,7 @@ fn test_mq_send_and_receive() {
5354
// panic, fork should never fail unless there is a serious problem with the OS
5455
Err(_) => panic!("Error: Fork Failed")
5556
}
57+
drop(m); //appease the unused_variable checker
5658
}
5759

5860

test/test_unistd.rs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ use std::ffi::CString;
99
use std::fs::File;
1010
use std::io::Write;
1111
use std::os::unix::prelude::*;
12-
use std::env::current_dir;
1312
use tempfile::tempfile;
1413
use tempdir::TempDir;
15-
use libc::off_t;
14+
use libc::{_exit, off_t};
1615

1716
#[test]
1817
fn test_fork_and_waitpid() {
18+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
1919
let pid = fork();
2020
match pid {
21-
Ok(Child) => {} // ignore child here
21+
Ok(Child) => { unsafe { _exit(0) }; }
2222
Ok(Parent { child }) => {
2323
// assert that child was created and pid > 0
2424
let child_raw: ::libc::pid_t = child.into();
@@ -29,23 +29,26 @@ fn test_fork_and_waitpid() {
2929
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),
3030

3131
// panic, must never happen
32-
Ok(_) => panic!("Child still alive, should never happen"),
32+
s @ Ok(_) => panic!("Child exited {:?}, should never happen", s),
3333

3434
// panic, waitpid should never fail
35-
Err(_) => panic!("Error: waitpid Failed")
35+
Err(s) => panic!("Error: waitpid returned Err({:?}", s)
3636
}
3737

3838
},
3939
// panic, fork should never fail unless there is a serious problem with the OS
4040
Err(_) => panic!("Error: Fork Failed")
4141
}
42+
drop(m); // appease the unused_variable checker
4243
}
4344

4445
#[test]
4546
fn test_wait() {
47+
// grab FORK_MTX so wait doesn't reap a different test's child process
48+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
4649
let pid = fork();
4750
match pid {
48-
Ok(Child) => {} // ignore child here
51+
Ok(Child) => { unsafe { _exit(0) }; }
4952
Ok(Parent { child }) => {
5053
let wait_status = wait();
5154

@@ -55,6 +58,7 @@ fn test_wait() {
5558
// panic, fork should never fail unless there is a serious problem with the OS
5659
Err(_) => panic!("Error: Fork Failed")
5760
}
61+
drop(m); // appease the unused_variable checker
5862
}
5963

6064
#[test]
@@ -100,6 +104,7 @@ macro_rules! execve_test_factory(
100104
($test_name:ident, $syscall:ident, $unix_sh:expr, $android_sh:expr) => (
101105
#[test]
102106
fn $test_name() {
107+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
103108
// The `exec`d process will write to `writer`, and we'll read that
104109
// data from `reader`.
105110
let (reader, writer) = pipe().unwrap();
@@ -139,46 +144,47 @@ macro_rules! execve_test_factory(
139144
assert!(string.contains("baz=quux"));
140145
}
141146
}
147+
drop(m); // appease the unused_variable checker
142148
}
143149
)
144150
);
145151

146152
#[test]
147153
fn test_fchdir() {
154+
// fchdir changes the process's cwd
155+
let m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test");
148156
let tmpdir = TempDir::new("test_fchdir").unwrap();
149157
let tmpdir_path = tmpdir.path().canonicalize().unwrap();
150158
let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd();
151-
let olddir_path = getcwd().unwrap();
152-
let olddir_fd = File::open(&olddir_path).unwrap().into_raw_fd();
153159

154160
assert!(fchdir(tmpdir_fd).is_ok());
155161
assert_eq!(getcwd().unwrap(), tmpdir_path);
156162

157-
assert!(fchdir(olddir_fd).is_ok());
158-
assert_eq!(getcwd().unwrap(), olddir_path);
159-
160-
assert!(close(olddir_fd).is_ok());
161163
assert!(close(tmpdir_fd).is_ok());
164+
drop(m); // appease the unused_variable checker
162165
}
163166

164167
#[test]
165168
fn test_getcwd() {
169+
// chdir changes the process's cwd
170+
let m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test");
166171
let tmp_dir = TempDir::new("test_getcwd").unwrap();
167172
assert!(chdir(tmp_dir.path()).is_ok());
168-
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
173+
assert_eq!(getcwd().unwrap(), tmp_dir.path());
169174

170-
// make path 500 chars longer so that buffer doubling in getcwd kicks in.
171-
// Note: One path cannot be longer than 255 bytes (NAME_MAX)
172-
// whole path cannot be longer than PATH_MAX (usually 4096 on linux, 1024 on macos)
175+
// make path 500 chars longer so that buffer doubling in getcwd
176+
// kicks in. Note: One path cannot be longer than 255 bytes
177+
// (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
178+
// 4096 on linux, 1024 on macos)
173179
let mut inner_tmp_dir = tmp_dir.path().to_path_buf();
174180
for _ in 0..5 {
175181
let newdir = iter::repeat("a").take(100).collect::<String>();
176-
//inner_tmp_dir = inner_tmp_dir.join(newdir).path();
177182
inner_tmp_dir.push(newdir);
178183
assert!(mkdir(inner_tmp_dir.as_path(), stat::S_IRWXU).is_ok());
179184
}
180185
assert!(chdir(inner_tmp_dir.as_path()).is_ok());
181-
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
186+
assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
187+
drop(m); // appease the unused_variable checker
182188
}
183189

184190
#[test]

0 commit comments

Comments
 (0)