crates/heimdall-container/src/exec.rs
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB
//! The job's `execve`, retried while the program is "busy" (ETXTBSY).
//!
//! Heimdall is a defensive sandbox. The runner unpacks every job's root
//! itself, in a process that runs other jobs at the same time: while one
//! job's `/bin/busybox` is open for writing, another thread's spawn (the
//! shim of another job) briefly holds a copy of that write descriptor
//! until its own `exec` closes it (`O_CLOEXEC`). If the first job reaches
//! `execve` inside that window, the kernel answers ETXTBSY ("Text file
//! busy"). Measured on oden: 1 run in 3 of the parallel container tests
//! under load. heimdall-plain's `forklock` closes that window for the
//! runner's own spawns; a host program that forks outside it (Gunnar
//! spawning git, Holger its workers) can still hold such a copy, and the
//! holder is always a transient fork. So the job's executor also retries
//! the `execve` for a bounded time ([`TRIES`] x [`PAUSE`]); any other
//! error, or a program still busy after that, is returned as it was.
use std::time::Duration;
/// How many times the executor tries `execve`.
pub const TRIES: u32 = 50;
/// The pause between two tries.
pub const PAUSE: Duration = Duration::from_millis(20);
/// Call `exec` (an `execve` that returns only on failure, with its errno)
/// until it fails with something other than ETXTBSY, at most `tries` times.
/// Returns the last errno.
pub fn retry_text_busy(tries: u32, pause: Duration, mut exec: impl FnMut() -> i32) -> i32 {
let mut n = 0;
loop {
n += 1;
let e = exec();
if e != libc::ETXTBSY || n >= tries {
return e;
}
std::thread::sleep(pause);
}
}