| Crates.io | luhproc |
| lib.rs | luhproc |
| version | 0.1.2 |
| created_at | 2025-12-07 01:29:55.392883+00 |
| updated_at | 2025-12-07 01:32:42.114172+00 |
| description | A lightweight background process manager |
| homepage | |
| repository | https://github.com/calizoots/luhtwin |
| max_upload_size | |
| id | 1970997 |
| size | 76,162 |
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:
MY_TASK=1)This makes it easy to run small persistent tasks (indexers, watchers, refreshers, schedulers, etc.) without needing systemd, Docker, or any external supervisor.
Add luhtwin to your Cargo.toml:
[dependencies]
luhproc = "0.1"
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")?;
}
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 ./myapp
The 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
ChildTaskRepresents 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 itselfEach task instance gets its own hashed directory:
my-task-3fa92k19cd12
This allows multiple instances of the same task type.
ProcessManagerMain API for controlling worker tasks.
pub struct ProcessManager {
pub tasks: Vec<ChildTask>,
}
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,
}?;
Spawns a background process by re-invoking the binary with an env var:
pm.start("MY_TASK", "session42", None)?;
Internally:
pm.stop("MY_TASK", Some("session42"))?;
Or stop all workers of that type:
pm.stop("MY_TASK", None)?;
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
/tmp/luhproc/
/
/my-task-ae92f139ab23/
/ pid # process ID
/ out.log # stdout of worker
/ err.log # stderr of worker
If LUHPROC_TMP_DIR is set, that directory is used instead of /tmp or dev temp.
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(())
}
SIGTERM (Unix-only via nix).stop() will report an error but still clean up.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)?;