Repository navigation
Description
The suspend_thread function in minidump_writer uses ptrace::attach() followed by waitpid() to capture the thread's SIGSTOP signal. However, if another thread (or process) calls waitpid() on the same PID before or during this operation, it will "steal" the stop notification, causing the suspend_thread function to hang indefinitely.
fn suspend_thread(child: Pid) -> Result<(), WriterError> {
// ...
ptrace::attach(pid).map_err(|e| AttachErr(child, e))?;
loop {
match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) { // <<< HANGS HEREwaitpid is used by std::process::Child::wait and std::process::Child::wait_with_output and thus waiting for the children to exit may "steal" state change and hang suspend_thread indefinitely.
For example the separate thread uses waitpid in EmbarkStudios/crash-handling/minidumper-test#L213, but the problem does not arise there because:
- It uses
cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped()), - child doesn't close stdout and stderr file descriptors early (see comment below),
child.wait_with_output()first reads from pipes until EOF that delayswaitpidcall until the minidump-writer detaches.
Possible solutions
It is not easy to fix without introducing artificial timeouts or more complex synchronization.
However, at least it's probably worth mentioning this issue in the suspend_thread documentation.
Something like:
/// Suspends a thread by attaching to it.
///
/// ## Race Condition Hazard
///
/// If another thread (or process) calls `waitpid()` on the same PID (e.g.,
/// via `Child::wait()`, `Child::wait_with_output()`, or direct syscall),
/// while this function is running, it may "steal" the stop notification,
/// causing this function to hang indefinitely.
///
/// This is because `ptrace::attach()` sends `SIGSTOP`, and the stop
/// notification is consumed by the first caller of `waitpid()`.
///
/// Ensure no concurrent `Child::wait()`, `Child::wait_with_output()` or
/// `waitpid()` calls on the same process.
fn suspend_thread(child: Pid) -> Result<(), WriterError> {Example
fn main() {
if std::env::args().nth(1).as_deref() != Some("--app") {
guardian();
} else {
app()
}
}
fn guardian() {
let app = std::process::Command::new(std::env::current_exe().unwrap())
.arg("--app")
// .stdout(std::process::Stdio::piped()) // Fix
// .stderr(std::process::Stdio::piped()) // Fix
.spawn()
.unwrap();
let pid = app.id().cast_signed();
let writer = minidump_writer::minidump_writer::MinidumpWriterConfig::new(pid, pid);
let mut file = std::fs::File::create("target/dump.dmp").unwrap();
let t1 = std::thread::spawn(move || writer.write(&mut file));
// std::thread::sleep(std::time::Duration::from_secs(1)); // FIX
let t2 = std::thread::spawn(move || {
let output = app.wait_with_output().unwrap();
println!("{}", output.status);
});
t1.join().unwrap().unwrap();
t2.join().unwrap();
}
fn app() {
std::thread::sleep(std::time::Duration::from_secs(10));
}If example started the wait_with_output results in status "stopped (not terminated) by signal: 19 (SIGSTOP)" and suspend_thread hangs indefinitely.
If any of the line marked as Fix is uncommented, wait_with_output returns only after dump is prepared with status "exit status: 0".
References
https://man7.org/linux/man-pages/man3/wait.3p.html
The behavior when multiple threads are blocked in wait(), waitid(), or waitpid() is described in Section 2.13, Status Information.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_13
Section 2.13, Status Information.
...
The parent obtains a child's status information by calling wait(), waitid(), or waitpid(). Except when waitid() is called with the WNOWAIT flag set in the options argument, the status information obtained by a wait function shall be consumed (discarded) by that wait function; no two calls to wait(), waitid() (without WNOWAIT), or waitpid() shall obtain the same status information.
Activity
[-]`waitpid` in `suspend_thread` may hang indefinitely if another thread calls `waitpid()` on the same PID[/-][+]`waitpid` in `suspend_thread` may hang indefinitely if another thread calls `waitpid` on the same PID[/+]zheland commentedon Feb 6, 2026
wait_with_output()doesn't prevent race condition if the child has closed stdout and stderr file descriptors early with something like: