use std::io;
use std::thread::{Builder, JoinHandle};
pub(crate) fn spawn<F>(name: impl Into<String>, run: F) -> io::Result<JoinHandle<()>>
where
F: FnOnce() + Send + 'static,
{
Builder::new()
.name(name.into())
.stack_size(harn_vm::RUNTIME_STACK_SIZE)
.spawn(run)
}
pub(crate) fn spawn_or_panic<F>(name: &'static str, run: F) -> JoinHandle<()>
where
F: FnOnce() + Send + 'static,
{
spawn(name, run).unwrap_or_else(|error| panic!("spawn {name} VM thread: {error}"))
}
#[cfg(test)]
mod tests {
#[inline(never)]
fn burn_stack(depth: usize) -> u8 {
let mut frame = [0u8; 16 * 1024];
frame[depth % frame.len()] = depth as u8;
let frame = std::hint::black_box(frame);
if depth == 0 {
return frame[0];
}
frame[0].wrapping_add(burn_stack(depth - 1))
}
const PROBE_CHILD: &str = "HARN_SERVE_VM_THREAD_STACK_PROBE";
#[test]
fn spawned_threads_get_more_than_the_default_stack() {
if std::env::var_os(PROBE_CHILD).is_some() {
let handle = super::spawn("vm-thread-stack-probe", || {
std::hint::black_box(burn_stack(4 * 1024 * 1024 / (16 * 1024)));
})
.expect("spawn probe thread");
handle.join().expect("probe thread completed");
return;
}
let status =
std::process::Command::new(std::env::current_exe().expect("current test executable"))
.args([
"--exact",
"vm_thread::tests::spawned_threads_get_more_than_the_default_stack",
"--test-threads=1",
])
.env(PROBE_CHILD, "1")
.env_remove("RUST_MIN_STACK")
.status()
.expect("re-exec the probe without RUST_MIN_STACK");
assert!(
status.success(),
"a thread from `vm_thread::spawn` overflowed on 4 MiB with \
RUST_MIN_STACK unset ({status}), so it inherited the 2 MiB default \
instead of harn_vm::RUNTIME_STACK_SIZE — that is what every shipped \
`harn serve` transport would run the VM on"
);
}
}