8000 `waitpid` in `suspend_thread` may hang indefinitely if another thread calls `waitpid` on the same PID · Issue #171 · rust-minidump/minidump-writer · GitHub
[go: up one dir, main page]

Skip to content

waitpid in suspend_thread may hang indefinitely if another thread calls waitpid on the same PID #171

Open

Description

@zheland

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 HERE

waitpid 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 delays waitpid call 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

  1. changed the title [-]`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[/+] on Feb 6, 2026
  2. zheland commented on Feb 6, 2026

    @zheland
    Author

    wait_with_output() doesn't prevent race condition if the child has closed stdout and stderr file descriptors early with something like:

    unsafe {
        libc::close(libc::STDOUT_FILENO);
        libc::close(libc::STDERR_FILENO);
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0