makepad/libs/relax/compiler/tests/modules/guard_threads.rs
Admin b22a714840 makepad run: the Rust JIT (libs/relax) and makepad-remote
The JIT that runs makepad apps from source: lexer, front end, type checker,
lazy borrow checker (Gandalf; `--check-first` opts in), codegen, session and
warm/cold caches keyed by compiler stamp, provided crates bound late,
startup images, live patching. User-facing name is `makepad run` (binary
`makepad`, MAKEPAD_RUN_* env, ~/.makepad-run); libs/relax stays the internal
library name. `makepad check` reports rustc-style diagnostics with rustc
error codes and checks a whole workspace (`--workspace`). Runs on macOS,
Linux, Windows and Android. makepad-remote: one tool schema for apps and the
WM (app/1): grabs, captures, traces, frame profiles and tweaks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

77 lines
3.1 KiB
Rust

// Run with MAKEPAD_RUN_SANDBOX_FUEL=0: these must be watchdog errors, not fuel exhaustion.
use std::relax::{guard, Module};
use std::sync::{Arc, Barrier};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
fn module(name: &str) -> Module {
Module::load_source(name, "guard_threads_module.rs", r#"
pub fn quick(x: u64) -> u64 { x.wrapping_add(1) }
pub fn slow() { loop { std::hint::spin_loop(); } }
"#, &[]).unwrap()
}
#[test]
fn concurrent_and_nested() {
// Separate arenas: simultaneous calls must not share the module's mutable state.
let slow_module = module("deadline_slow");
let fast_module = module("deadline_fast");
let slow: fn() = slow_module.get("slow").unwrap();
let quick: fn(u64) -> u64 = fast_module.get("quick").unwrap();
assert_eq!(guard(1000, || quick(1)).unwrap(), 2);
let start = Arc::new(Barrier::new(2));
let done = Arc::new(AtomicBool::new(false));
let start_slow = start.clone();
let done_slow = done.clone();
let slow_thread = std::thread::spawn(move || {
start_slow.wait();
let now = Instant::now();
assert!(guard(150, || slow()).is_err());
assert!(now.elapsed() < Duration::from_secs(3));
done_slow.store(true, Ordering::SeqCst);
});
let fast_thread = std::thread::spawn(move || {
start.wait();
let now = Instant::now();
let mut calls = 0;
while !done.load(Ordering::SeqCst) || calls < 100 {
assert_eq!(guard(1000, || quick(calls)).unwrap(), calls + 1);
calls += 1;
// Repeated arm/disarm overlaps the slow deadline, with time for signal delivery.
std::thread::sleep(Duration::from_millis(1));
assert!(now.elapsed() < Duration::from_secs(15));
}
});
slow_thread.join().unwrap();
fast_thread.join().unwrap();
println!("out concurrent guards: true");
// Inner completion must leave the outer deadline armed.
assert!(guard(100, || {
assert_eq!(guard(1000, || quick(4)).unwrap(), 5);
slow();
}).is_err());
// An unbounded inner guard cannot extend the outer deadline. The outer guard
// owns this timeout; control must never return to its closure after the inner call.
let returned_inner = Arc::new(AtomicBool::new(false));
let flag = returned_inner.clone();
assert!(guard(50, || {
let _ = guard(0, || slow());
flag.store(true, Ordering::SeqCst);
}).is_err());
assert!(!returned_inner.load(Ordering::SeqCst));
// Inner timeout must not cancel or mark the outer call as timed out.
assert_eq!(guard(2000, || {
assert!(guard(50, || slow()).is_err());
guard(0, || quick(6)).unwrap()
}).unwrap(), 7);
// A completed short timer cannot interrupt a subsequent no-deadline call.
for _ in 0..100 { assert_eq!(guard(20, || quick(0)).unwrap(), 1); }
assert_eq!(guard(0, || {
std::thread::sleep(Duration::from_millis(40));
quick(8)
}).unwrap(), 9);
println!("out nested guards: true");
slow_module.unload();
fast_module.unload();
}