use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use crate::runtime::ExecError;
pub fn with_timeout<F, T>(timeout: Duration, work: F) -> Result<T, ExecError>
where
F: FnOnce() -> Result<T, ExecError> + Send + 'static,
T: Send + 'static,
{
let (tx, rx) = mpsc::sync_channel::<Result<T, ExecError>>(1);
thread::Builder::new()
.name("outl-exec".into())
.spawn(move || {
let _ = tx.send(work());
})
.map_err(|e| ExecError::Sandbox(format!("spawn worker: {e}")))?;
match rx.recv_timeout(timeout) {
Ok(result) => result,
Err(mpsc::RecvTimeoutError::Timeout) => Err(ExecError::Timeout(timeout)),
Err(mpsc::RecvTimeoutError::Disconnected) => Err(ExecError::Sandbox(
"worker thread vanished without a result".into(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fast_work_returns_value() {
let v: Result<i32, ExecError> = with_timeout(Duration::from_secs(2), || Ok(42));
assert!(matches!(v, Ok(42)));
}
#[test]
fn slow_work_times_out() {
let v: Result<(), ExecError> = with_timeout(Duration::from_millis(50), || {
std::thread::sleep(Duration::from_millis(500));
Ok(())
});
assert!(matches!(v, Err(ExecError::Timeout(_))));
}
#[test]
fn inner_error_propagates() {
let v: Result<(), ExecError> = with_timeout(Duration::from_secs(2), || {
Err(ExecError::Language("oops".into()))
});
match v {
Err(ExecError::Language(m)) => assert_eq!(m, "oops"),
other => panic!("expected Language error, got {other:?}"),
}
}
}