Expand description
§luhproc
A lightweight background process manager
made with love s.c
luhproc provides a simple system for spawning, tracking, and stopping
background worker processes in Rust applications.
It is built around two core ideas:
- Workers are identified by environment variables (e.g.
MY_TASK=1) - Workers run in background mode when started from the same binary
This makes it easy to run small persistent tasks (indexers, watchers, refreshers, schedulers, etc.) without needing systemd, Docker, or any external supervisor.
§Quick Start
Add luhtwin to your Cargo.toml:
[dependencies]
luhproc = "0.0.1"§Example
static PM: OnceLock<ProcessManager> = OnceLock::new();
fn child_work() -> LuhTwin<()> {
info!("hello from the child thread");
Ok(())
}
fn main() -> LuhTwin<() {
PM.set(process_manager!("MOTHAPP" => start_moth)
.encase(|| "failed to make process_manager")?)
.unwrap();
PM.get().unwrap().check()
.encase(|| "failed to run child process")?;
PM.get().unwrap().start("MOTHAPP", "app", None)
.encase(|| "failed to start moth app")?;
}§Basic Concept
You define tasks using the process_manager! macro:
process_manager! {
"MY_TASK" => my_background_function,
"REFRESH_CACHE" => refresh_cache_worker,
};A task is triggered when the binary starts with that environment variable set.
For example:
MY_TASK=1 ./myappThe main process will immediately run the function associated with the task, then exit after finishing.
The task can be launched in the background through the API:
pm.start("MY_TASK", "unique-id", None)?;Each running worker is stored inside a temporary directory containing:
<tmp>/luhproc/my-task-<hash>/
├── out.log # stdout
├── err.log # stderr
└── pid # process ID§Architecture
§ChildTask
Represents one runnable background task.
pub struct ChildTask {
pub id: String,
pub env_var: &'static str,
pub work: fn() -> LuhTwin<()>,
}id— unique instance identifier (affects directory hashing)env_var— environment variable that triggers the workerwork— the task function itself
§Generated Directory Name
Each task instance gets its own hashed directory:
my-task-3fa92k19cd12This allows multiple instances of the same task type.
§ProcessManager
Main API for controlling worker tasks.
pub struct ProcessManager {
pub tasks: Vec<ChildTask>,
}§Registering Tasks
let mut pm = ProcessManager::new()?;
pm.register_task("MY_TASK", my_worker_fn);Or with the macro:
let pm = process_manager! {
"MY_TASK" => my_worker_fn,
"SYNC" => sync_worker_fn,
}?;§Starting a Worker
Spawns a background process by re-invoking the binary with an env var:
pm.start("MY_TASK", "session42", None)?;Internally:
- creates temp directory
- forks current executable
- writes PID file
- redirects stdout/stderr
§Stopping a Worker
pm.stop("MY_TASK", Some("session42"))?;Or stop all workers of that type:
pm.stop("MY_TASK", None)?;§Checking Worker Status
let details = pm.info("MY_TASK", "session42")?;
println!("{details}");Example output:
task: MY_TASK (id: session42)
pid: 39241
directory: /tmp/luhproc/my-task-a8fd93c2e1a3
log file: /tmp/luhproc/.../out.log
error file: /tmp/luhproc/.../err.log§File Layout
/tmp/luhproc/
/
/my-task-ae92f139ab23/
/ pid # process ID
/ out.log # stdout of worker
/ err.log # stderr of workerIf LUHPROC_TMP_DIR is set, that directory is used instead of /tmp or dev temp.
§Environment Trigger Check
At the start of your application, call:
pm.check()?;If the process was started as a worker, the task runs inline and the parent process exits afterwards.
Your main typically looks like:
fn main() -> LuhTwin<()> {
let pm = process_manager! {
"MY_TASK" => run_worker,
}?;
// check for background-worker mode
pm.check()?;
// normal application logic
run_cli()?;
Ok(())
}§Behavior Notes
- Workers are single-process, not threadpools.
- Stopping uses
SIGTERM(Unix-only vianix). - If a PID file becomes invalid,
stop()will report an error but still clean up. - If your worker loops forever, ensure it handles SIGTERM gracefully.
§Example Worker
fn ping_worker() -> LuhTwin<()> {
loop {
println!("ping!");
std::thread::sleep(std::time::Duration::from_secs(1));
}
}And launching it:
let pm = process_manager! {
"PING_WORKER" => ping_worker,
}?;
pm.check()?;
pm.start("PING_WORKER", "main", None)?;Macros§
- process_
manager - Convenience macro that constructs a
ProcessManagerand registers multiple tasks in a compact syntax.
Structs§
- Child
Task - Represents a background task that can be launched by the
ProcessManager. - Process
Manager - Central controller responsible for registering tasks, launching background workers, inspecting them, and stopping them.
Statics§
- TMP_DIR
- Just a global PathBuf for the TMP_DIR