#process-manager #script #process #manager #command #subprocess #child-process

proc-heim

Library for running and managing short-lived and long-lived processes using asynchronous API

2 releases

0.1.1 Jan 21, 2025
0.1.0 Jan 21, 2025

#239 in Asynchronous

Download history 64/week @ 2025-01-15 115/week @ 2025-01-22

179 downloads per month

MIT/Apache

150KB
2.5K SLoC

Proc-heim

Proc-heim is a library for running and managing short-lived and long-lived processes using asynchronous API. A new process can be created by running either command or script.

Features

Proc-heim internally uses tokio::process for executing processes and provides all its functionality plus additional features:

  • spawning new processes via scripts (in different scripting languages) and any Rust types, which implements Runnable trait,
  • flexible managing of all spawned processes using single facade, which can be easily shared by multiple threads/tasks,
  • bi-directional, message-based communication between child and parent processes via standard IO streams or named pipes,
  • collecting and querying logs produced by child processes (both running and completed).

For more detailed list of features see ProcessManagerHandle documentation.

API overview

Proc-heim library is divided into two modules: model and manager.

The first one defines types and traits used to describe commands, scripts and their settings, such as messaging and logging type, environment variables and working directory. The module contains a Runnable trait, which defines how to run a user-defined process. The library provides two implementation of this trait: Cmd and Script.

The manager module provides an API for spawning and managing child processes. The whole implementation relies on Actor model architecture. To start using the library, a client code needs to spawn a ProcessManager task, responsible for:

  • creating new actors implementing some functionality (eg. reading messages from child process),
  • forwarding messages sent between client code and other actors.

After spawning the ProcessManager task, a ProcessManagerHandle is being returned, which exposes an API for spawning and managing user-defined processes.

Examples

Spawning a new ProcessManager task

use proc_heim::manager::ProcessManager;
use std::path::PathBuf;

let working_directory = PathBuf::from("/some/temp/path");
let handle = ProcessManager::spawn(working_directory)?;
// now use the handle to spawn new processes and interact with them

Spawning a new process from command

use proc_heim::manager::ProcessManager;
use std::path::PathBuf;

let working_directory = PathBuf::from("/tmp/proc_heim");
let handle = ProcessManager::spawn(working_directory)?;
let cmd = Cmd::with_args("ls", ["-l", "/some/dir"]);
let process_id = handle.spawn(cmd).await?;
// now use the process_id to interact with a process ...

Reading logs from a process

use proc_heim::{
    manager::{LogsQuery, ProcessManager},
    model::{
        command::{CmdOptions, LoggingType},
        script::{Script, ScriptingLanguage}
    },
};
use std::{path::PathBuf, time::Duration};

let working_directory = PathBuf::from("/tmp/proc_heim");
    let handle = ProcessManager::spawn(working_directory)?;
    let script = Script::with_args_and_options(
        ScriptingLanguage::Bash,
        r#"
        echo 'Simple log example'
        echo "Hello $1"
        echo 'Error log' >&2
        "#,
        ["World"],
        CmdOptions::with_logging(LoggingType::StdoutAndStderr),
    );

    let process_id = handle.spawn(script).await?;
    // We are waiting for the process to exit in order to get all logs
    handle.wait(process_id, Duration::from_micros(10)).await??;

    let logs = handle
        .get_logs_stdout(process_id, LogsQuery::with_offset(1))
        .await?;
    assert_eq!(1, logs.len());
    assert_eq!("Hello World", logs[0]);

    let error_logs = handle
        .get_logs_stderr(process_id, LogsQuery::fetch_all())
        .await?;
    assert_eq!(1, error_logs.len());
    assert_eq!("Error log", error_logs[0]);

Messaging with a process via named pipes

use futures::TryStreamExt;
use proc_heim::{
    manager::ProcessManager,
    model::{
        command::CmdOptions,
        script::{Script, ScriptingLanguage},
    },
};
use std::path::PathBuf;

let working_directory = PathBuf::from("/tmp/proc_heim");
let handle = ProcessManager::spawn(working_directory)?;
let script = Script::with_options(
    ScriptingLanguage::Bash,
    r#"
    counter=0
    while read msg; do
        echo "$counter: $msg" > $OUTPUT_PIPE
        counter=$((counter + 1))
    done < $INPUT_PIPE
    "#,
    CmdOptions::with_named_pipe_messaging(), // we want to send messages bidirectionally
);

// We can use "spawn_with_handle" instead of "spawn" to get "ProcessHandle",
// which mimics the "ProcessManagerHandle" API, 
// but without having to pass the process ID to each method call.
let process_handle = handle.spawn_with_handle(script).await?;

process_handle.send_message("First message").await?;
// We can send a next message without causing a deadlock here.
// This is possible because the response to the first message
// will be read by a dedicated Tokio task, 
// spawned automatically by the Process Manager.
process_handle.send_message("Second message").await?;

let mut stream = process_handle.subscribe_message_string_stream().await?;

let msg = stream.try_next().await?.unwrap();
assert_eq!("0: First message", msg);

let msg = stream.try_next().await?.unwrap();
assert_eq!("1: Second message", msg);

let result = process_handle.kill().await;
assert!(result.is_ok());

For more examples, see integration tests.

Dependencies

~6–15MB
~186K SLoC