Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow waiting for the process multiple times #3790

Closed
VictorBulba opened this issue May 16, 2021 · 9 comments
Closed

Allow waiting for the process multiple times #3790

VictorBulba opened this issue May 16, 2021 · 9 comments
Labels
A-tokio Area: The main tokio crate C-feature-request Category: A feature request. M-process Module: tokio/process

Comments

@VictorBulba
Copy link

I am trying to use ptrace for tracing processes created with tokio Command. I need to wait for the process multiple times. But wait is guaranteed to return same cached status. I would like to have an async api that make waitpid every time it is called.

@VictorBulba VictorBulba added A-tokio Area: The main tokio crate C-feature-request Category: A feature request. labels May 16, 2021
@Darksonn Darksonn added the M-process Module: tokio/process label May 16, 2021
@VictorBulba
Copy link
Author

Hey. Please help.

Currently I am trying to implement async version of waitpid. I need to wait for the SIGCHLD, but endless awaiting occurs.

nix::sys::ptrace::syscall(pid, None).unwrap(); // This sends SIGCHLD
let kind = tokio::signal::unix::SignalKind::child();
let mut signal = tokio::signal::unix::signal(kind).unwrap();
signal.recv().await; // endless awaiting
// This won't be executed.

ptrace::syscall definitely sends SIGCHLD because I can handle it with handlers set using libc's signal

@MikailBag
Copy link
Contributor

Maybe you are hitting race condition? I think it is possible that kernel delivers SIGCHLD faster than you create a signal stream. If this is the case, changing order so that signal() stream is created prior to the ptrace should help.

@VictorBulba
Copy link
Author

I tried to set the following code at the start of the program but it prints only "Awaiting SIGCHLD"

tokio::spawn(async {
    let kind = tokio::signal::unix::SignalKind::child();
    loop {
        let mut signal = tokio::signal::unix::signal(kind).unwrap();
        println!("Awaiting SIGCHLD");
        signal.recv().await;
        println!("Got SIGCHLD");
    }
});

@VictorBulba
Copy link
Author

VictorBulba commented May 18, 2021

Look at this code:

use std::os::unix::process::CommandExt;
use nix::sys::{wait, ptrace};


#[tokio::main]
async fn main() -> std::io::Result<()> {
    // This handles only exit SIGCHLD signal
    tokio::spawn(async {
        let kind = tokio::signal::unix::SignalKind::child();
        loop {
            let mut signal = tokio::signal::unix::signal(kind).unwrap();
            println!("Awaiting SIGCHLD");
            signal.recv().await;
            println!("Got SIGCHLD");
        }
    });

    // This handles all SIGCHLD signals
    // let handler = nix::sys::signal::SigHandler::Handler(handle_signal);
    // unsafe {
    //     nix::sys::signal::signal(nix::sys::signal::SIGCHLD, handler).unwrap();
    // }

    run_command().await;
    
    println!("Bye");
    Ok(())
}

extern fn handle_signal(signal: i32) {
    println!("Signal {}", signal);
}

async fn run_command() {
    // libc::raise(libc::SIGCHLD);

    let mut command = std::process::Command::new("ls");
    unsafe {
        command.pre_exec(start_traceme);
    }

    let child = command.spawn().unwrap();
    let pid = nix::unistd::Pid::from_raw(child.id() as _);

    wait::waitpid(pid, None).unwrap();
    ptrace::setoptions(pid, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();

    loop {
        ptrace::syscall(pid, None).unwrap();
        // tokio::task::yield_now().await;
        wait::waitpid(pid, None).unwrap();

        let state = ptrace::getregs(pid).unwrap();
        println!("SYSCALL {}", state.orig_rax);

        ptrace::syscall(pid, None).unwrap();
        let status = wait::waitpid(pid, None).unwrap();
        if let wait::WaitStatus::Exited(_, code) = status {
            println!("Exit {}", code);
            return
        }
    }
}

fn start_traceme() -> std::io::Result<()> {
    ptrace::traceme().map_err(|err| {
        let kind = std::io::ErrorKind::Other;
        std::io::Error::new(kind, err)
    })
}

@ipetkov
Copy link
Member

ipetkov commented May 18, 2021

@VictorBulba tokio signal handlers are registered lazily, so if the signal handler is registered after the command is spawned, it's entirely possible the OS will deliver the signal before the code to register the handler has actually run.

Could you try registering a tokio signal listener first before spawning a command?

@ipetkov
Copy link
Member

ipetkov commented May 18, 2021

Responding to the original message:

I am trying to use ptrace for tracing processes created with tokio Command. I need to wait for the process multiple times. But wait is guaranteed to return same cached status. I would like to have an async api that make waitpid every time it is called.

The tokio child process implementation delegates to the standard library which does cache exit results. I'm not against some kind of API which allows for getting non-exit child statuses, but it will need to be a new API since changing the semantics of the existing APIs can be considered a breaking change (and surprising behavior compared to what std does).

FWIW it seems like there are some nightly APIs for checking if a child was stopped: https://doc.rust-lang.org/stable/std/os/unix/process/trait.ExitStatusExt.html#tymethod.stopped_signal (tracked in rust-lang/rust#80695 )

@VictorBulba
Copy link
Author

VictorBulba commented May 19, 2021

@VictorBulba tokio signal handlers are registered lazily, so if the signal handler is registered after the command is spawned, it's entirely possible the OS will deliver the signal before the code to register the handler has actually run.

Could you try registering a tokio signal listener first before spawning a command?

Look at the code above, it spawns a task that starts awaiting for the SIGCHLD before the command starts.

@VictorBulba
Copy link
Author

Responding to the original message:

I am trying to use ptrace for tracing processes created with tokio Command. I need to wait for the process multiple times. But wait is guaranteed to return same cached status. I would like to have an async api that make waitpid every time it is called.

The tokio child process implementation delegates to the standard library which does cache exit results. I'm not against some kind of API which allows for getting non-exit child statuses, but it will need to be a new API since changing the semantics of the existing APIs can be considered a breaking change (and surprising behavior compared to what std does).

FWIW it seems like there are some nightly APIs for checking if a child was stopped: https://doc.rust-lang.org/stable/std/os/unix/process/trait.ExitStatusExt.html#tymethod.stopped_signal (tracked in rust-lang/rust#80695 )

Yeah, with nightly api it is possible to convert exit status into_raw status and then
WaitStatus::from_raw https://docs.rs/nix/0.20.0/nix/sys/wait/enum.WaitStatus.html

@VictorBulba
Copy link
Author

I'm closing this because I opened a new one with a similar problem #3804

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate C-feature-request Category: A feature request. M-process Module: tokio/process
Projects
None yet
Development

No branches or pull requests

4 participants