Skip to content

Commit 64f7984

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 std::process: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. Fixes #251
1 parent 386c50c commit 64f7984

File tree

5 files changed

+55
-26
lines changed

5 files changed

+55
-26
lines changed

test/sys/test_aio.rs

+4-5
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,8 @@ 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+
#[allow(unused_variables)]
247+
let m = ::SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test");
250248
let sa = SigAction::new(SigHandler::Handler(sigfunc),
251249
SA_RESETHAND,
252250
SigSet::empty());
@@ -376,7 +374,8 @@ 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+
#[allow(unused_variables)]
378+
let m = ::SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test");
380379
const INITIAL: &'static [u8] = b"abcdef123456";
381380
const WBUF: &'static [u8] = b"CDEF";
382381
let rbuf = Rc::new(vec![0; 4].into_boxed_slice());

test/sys/test_wait.rs

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use libc::exit;
66

77
#[test]
88
fn test_wait_signal() {
9+
#[allow(unused_variables)]
10+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
11+
912
match fork() {
1013
Ok(Child) => pause().unwrap_or(()),
1114
Ok(Parent { child }) => {
@@ -19,6 +22,9 @@ fn test_wait_signal() {
1922

2023
#[test]
2124
fn test_wait_exit() {
25+
#[allow(unused_variables)]
26+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
27+
2228
match fork() {
2329
Ok(Child) => unsafe { exit(12); },
2430
Ok(Parent { child }) => {

test/test.rs

+12
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

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use nix::Error::Sys;
1616

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

2022
const MSG_SIZE: c_long = 32;
2123
let attr = MqAttr::new(0, 10, MSG_SIZE, 0);

test/test_unistd.rs

+31-21
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ 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;
1514
use libc::off_t;
15+
use std::process::exit;
1616

1717
#[test]
1818
fn test_fork_and_waitpid() {
19+
#[allow(unused_variables)]
20+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
1921
let pid = fork();
2022
match pid {
21-
Ok(Child) => {} // ignore child here
23+
Ok(Child) => exit(0),
2224
Ok(Parent { child }) => {
2325
// assert that child was created and pid > 0
2426
let child_raw: ::libc::pid_t = child.into();
@@ -29,10 +31,10 @@ fn test_fork_and_waitpid() {
2931
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),
3032

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

3436
// panic, waitpid should never fail
35-
Err(_) => panic!("Error: waitpid Failed")
37+
Err(s) => panic!("Error: waitpid returned Err({:?}", s)
3638
}
3739

3840
},
@@ -43,9 +45,12 @@ fn test_fork_and_waitpid() {
4345

4446
#[test]
4547
fn test_wait() {
48+
// Grab FORK_MTX so wait doesn't reap a different test's child process
49+
#[allow(unused_variables)]
50+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
4651
let pid = fork();
4752
match pid {
48-
Ok(Child) => {} // ignore child here
53+
Ok(Child) => exit(0),
4954
Ok(Parent { child }) => {
5055
let wait_status = wait();
5156

@@ -100,6 +105,8 @@ macro_rules! execve_test_factory(
100105
($test_name:ident, $syscall:ident, $unix_sh:expr, $android_sh:expr) => (
101106
#[test]
102107
fn $test_name() {
108+
#[allow(unused_variables)]
109+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
103110
// The `exec`d process will write to `writer`, and we'll read that
104111
// data from `reader`.
105112
let (reader, writer) = pipe().unwrap();
@@ -145,40 +152,43 @@ macro_rules! execve_test_factory(
145152

146153
#[test]
147154
fn test_fchdir() {
155+
// fchdir changes the process's cwd
156+
#[allow(unused_variables)]
157+
let m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test");
158+
148159
let tmpdir = TempDir::new("test_fchdir").unwrap();
149160
let tmpdir_path = tmpdir.path().canonicalize().unwrap();
150161
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();
153162

154163
assert!(fchdir(tmpdir_fd).is_ok());
155164
assert_eq!(getcwd().unwrap(), tmpdir_path);
156165

157-
assert!(fchdir(olddir_fd).is_ok());
158-
assert_eq!(getcwd().unwrap(), olddir_path);
159-
160-
assert!(close(olddir_fd).is_ok());
161166
assert!(close(tmpdir_fd).is_ok());
162167
}
163168

164169
#[test]
165170
fn test_getcwd() {
166-
let tmp_dir = TempDir::new("test_getcwd").unwrap();
167-
assert!(chdir(tmp_dir.path()).is_ok());
168-
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
169-
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)
173-
let mut inner_tmp_dir = tmp_dir.path().to_path_buf();
171+
// chdir changes the process's cwd
172+
#[allow(unused_variables)]
173+
let m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test");
174+
175+
let tmpdir = TempDir::new("test_getcwd").unwrap();
176+
let tmpdir_path = tmpdir.path().canonicalize().unwrap();
177+
assert!(chdir(&tmpdir_path).is_ok());
178+
assert_eq!(getcwd().unwrap(), tmpdir_path);
179+
180+
// make path 500 chars longer so that buffer doubling in getcwd
181+
// kicks in. Note: One path cannot be longer than 255 bytes
182+
// (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
183+
// 4096 on linux, 1024 on macos)
184+
let mut inner_tmp_dir = tmpdir_path.to_path_buf();
174185
for _ in 0..5 {
175186
let newdir = iter::repeat("a").take(100).collect::<String>();
176-
//inner_tmp_dir = inner_tmp_dir.join(newdir).path();
177187
inner_tmp_dir.push(newdir);
178188
assert!(mkdir(inner_tmp_dir.as_path(), stat::S_IRWXU).is_ok());
179189
}
180190
assert!(chdir(inner_tmp_dir.as_path()).is_ok());
181-
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
191+
assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
182192
}
183193

184194
#[test]

0 commit comments

Comments
 (0)