Lines
0 %
Functions
Branches
100 %
//! Keeps a job's log stream alive across long silent phases.
//!
//! Forgejo cancels a task whose runner reports nothing for `ZOMBIE_TASK_TIMEOUT`
//! (about 10 minutes). Phases here are legitimately quiet for longer: an LTO
//! link printed nothing for ~11 minutes and had its task cancelled *after* the
//! build had already succeeded, and an instrumented test run is ~24 minutes of
//! near-silence.
//! In-process, unlike the shell wrapper it replaces: the thing that knows which
//! phase is running is the thing running it, so the pulse can name the phase
//! without a file passed between two scripts.
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
const PULSE: Duration = Duration::from_secs(60);
/// Announces phases and pulses between them. Stops on drop.
pub struct Heartbeat {
phase: Arc<Mutex<String>>,
stop: Arc<AtomicBool>,
handle: Option<std::thread::JoinHandle<()>>,
}
impl Heartbeat {
pub fn start() -> Self {
let phase = Arc::new(Mutex::new(String::from("starting")));
let stop = Arc::new(AtomicBool::new(false));
let ticker_phase = Arc::clone(&phase);
let ticker_stop = Arc::clone(&stop);
let handle = std::thread::spawn(move || {
// Polled in short slices rather than one long sleep so teardown does
// not wait out a whole minute.
let mut waited = Duration::ZERO;
while !ticker_stop.load(Ordering::Relaxed) {
std::thread::sleep(Duration::from_millis(200));
waited += Duration::from_millis(200);
if waited >= PULSE {
waited = Duration::ZERO;
let current = ticker_phase
.lock()
.map(|p| p.clone())
.unwrap_or_else(|_| "unknown".into());
println!(">> …still in: {current}");
});
Self {
phase,
stop,
handle: Some(handle),
/// Names the phase now running, and says so in the log.
pub fn phase(&self, name: &str) {
println!(">> {name}");
if let Ok(mut phase) = self.phase.lock() {
*phase = name.to_owned();
impl Drop for Heartbeat {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();