Crate proc_heim

Crate proc_heim 

Source
Expand description

§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).expect("Invalid 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;
use proc_heim::model::command::Cmd;

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, TryMessageStreamExt},
    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_stream()
    .await?
    .into_string_stream();

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.

Modules§

manager
API used for spawning and managing multiple processes.
model
Types and traits used for modeling commands, scripts and custom processes.