Spec-Zone.ru › Tokio

Модуль process

Доступен только при включённой функции crate process.

Реализация асинхронного управления процессами для Tokio.

Этот модуль предоставляет структуру Command, имитирующую интерфейс типа std::process::Command из стандартной библиотеки, но предоставляющую асинхронные версии функций, создающих процессы. Эти функции (spawn, status, output и их варианты) возвращают типы, «совместимые с future», которые взаимодействуют с Tokio. Асинхронная поддержка процессов обеспечивается посредством обработки сигналов в Unix и системных API в Windows.

Примеры

Ниже приведён пример программы, которая запускает echo hello world, а затем ожидает его завершения.

use tokio::process::Command;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The usage is similar as with the standard library's `Command` type
    let mut child = Command::new("echo")
        .arg("hello")
        .arg("world")
        .spawn()
        .expect("failed to spawn");

    // Await until the command completes
    let status = child.wait().await?;
    println!("the command exited with: {}", status);
    Ok(())
}

Теперь рассмотрим пример, в котором мы не только запускаем echo hello world, но и захватываем его вывод.

use tokio::process::Command;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Like above, but use `output` which returns a future instead of
    // immediately returning the `Child`.
    let output = Command::new("echo").arg("hello").arg("world")
                        .output();

    let output = output.await?;

    assert!(output.status.success());
    assert_eq!(output.stdout, b"hello world\n");
    Ok(())
}

Мы также можем читать ввод построчно.

use tokio::io::{BufReader, AsyncBufReadExt};
use tokio::process::Command;

use std::process::Stdio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::new("cat");

    // Specify that we want the command's standard output piped back to us.
    // By default, standard input/output/error will be inherited from the
    // current process (for example, this means that standard input will
    // come from the keyboard and standard output/error will go directly to
    // the terminal if this process is invoked from the command line).
    cmd.stdout(Stdio::piped());

    let mut child = cmd.spawn()
        .expect("failed to spawn command");

    let stdout = child.stdout.take()
        .expect("child did not have a handle to stdout");

    let mut reader = BufReader::new(stdout).lines();

    // Ensure the child process is spawned in the runtime so it can
    // make progress on its own while we await for any output.
    tokio::spawn(async move {
        let status = child.wait().await
            .expect("child process encountered an error");

        println!("child status was: {}", status);
    });

    while let Some(line) = reader.next_line().await? {
        println!("Line: {}", line);
    }

    Ok(())
}

Вот ещё один пример использования sort для записи в стандартный ввод дочернего процесса с захватом вывода отсортированного текста.

use tokio::io::AsyncWriteExt;
use tokio::process::Command;

use std::process::Stdio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::new("sort");

    // Specifying that we want pipe both the output and the input.
    // Similarly to capturing the output, by configuring the pipe
    // to stdin it can now be used as an asynchronous writer.
    cmd.stdout(Stdio::piped());
    cmd.stdin(Stdio::piped());

    let mut child = cmd.spawn().expect("failed to spawn command");

    // These are the animals we want to sort
    let animals: &[&str] = &["dog", "bird", "frog", "cat", "fish"];

    let mut stdin = child
        .stdin
        .take()
        .expect("child did not have a handle to stdin");

    // Write our animals to the child process
    // Note that the behavior of `sort` is to buffer _all input_ before writing any output.
    // In the general sense, it is recommended to write to the child in a separate task as
    // awaiting its exit (or output) to avoid deadlocks (for example, the child tries to write
    // some output but gets stuck waiting on the parent to read from it, meanwhile the parent
    // is stuck waiting to write its input completely before reading the output).
    stdin
        .write(animals.join("\n").as_bytes())
        .await
        .expect("could not write to stdin");

    // We drop the handle here which signals EOF to the child process.
    // This tells the child process that it there is no more data on the pipe.
    drop(stdin);

    let op = child.wait_with_output().await?;

    // Results should come back in sorted order
    assert_eq!(op.stdout, "bird\ncat\ndog\nfish\nfrog\n".as_bytes());

    Ok(())
}

С помощью дополнительной координации мы также можем передать вывод одной команды другой.

use tokio::join;
use tokio::process::Command;
use std::process::Stdio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut echo = Command::new("echo")
        .arg("hello world!")
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to spawn echo");

    let tr_stdin: Stdio = echo
        .stdout
        .take()
        .unwrap()
        .try_into()
        .expect("failed to convert to Stdio");

    let tr = Command::new("tr")
        .arg("a-z")
        .arg("A-Z")
        .stdin(tr_stdin)
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to spawn tr");

    let (echo_result, tr_output) = join!(echo.wait(), tr.wait_with_output());

    assert!(echo_result.unwrap().success());

    let tr_output = tr_output.expect("failed to await tr");
    assert!(tr_output.status.success());

    assert_eq!(tr_output.stdout, b"HELLO WORLD!\n");

    Ok(())
}

Предостережения

Удаление/отмена

Как и в стандартной библиотеке, и в отличие от парадигмы futures, где удаление означает отмену, запущенный процесс по умолчанию продолжит выполняться даже после удаления дескриптора Child.

Метод Command::kill_on_drop позволяет изменить это поведение и завершить дочерний процесс, если обёртка Child удалена до его завершения.

Процессы Unix

На платформах Unix родительский процесс должен «собирать» завершившиеся дочерние процессы, чтобы освободить все ресурсы ОС. Завершившийся дочерний процесс, который ещё не был собран родительским процессом, считается процессом-зомби. Такие процессы продолжают учитываться при применении системных ограничений, а слишком большое количество процессов-зомби может помешать запуску новых процессов.

Среда выполнения Tokio по мере возможности пытается собирать и очищать все запущенные ею процессы. Никаких дополнительных гарантий относительно того, как быстро и как часто выполняется эта процедура, не предоставляется.

Если необходимы более строгие гарантии очистки, рекомендуется не удалять дескриптор процесса Child, пока процесс не будет полностью awaited.

Структуры

Child
Представление дочернего процесса, запущенного в цикле обработки событий.
ChildStderr
Поток стандартного вывода ошибок для запущенных дочерних процессов.
ChildStdin
Поток стандартного ввода для запущенных дочерних процессов.
ChildStdout
Поток стандартного вывода для запущенных дочерних процессов.
Command
Эта структура имитирует API типа std::process::Command из стандартной библиотеки, но заменяет функции создания процесса их асинхронными вариантами. Основные предоставляемые асинхронные функции: spawn, status и output.

MIT License
Copyright © Tokio Contributors
https://docs.rs/tokio/1.53.1/tokio/process/index.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API