Lines
100 %
Functions
Branches
//! Lives in its own test binary: the assertion counts every thread in the
//! process, so a test running beside it in the same binary perturbs the count.
//! Cargo runs test targets one at a time, which makes the measurement exact.
use scripting::executor::ScriptExecutor;
use scripting::format::{ContextType, EntityType};
use scripting::serializer::MemorySerializer;
const TEST_WASM: &[u8] = include_bytes!("../../web/static/wasm/groceries_markup.wasm");
/// The epoch ticker must not outlive the execution that started it.
///
/// The epoch counter is shared by every execution on the same engine, so a
/// ticker that keeps running after its call spends the budget of whatever runs
/// next — and executors are reused (the account-script batch path drives one
/// across many transactions). A `JoinHandle` does not stop a thread when
/// dropped, it detaches it, so the previous `drop(ticker)` left it ticking, and
/// every `?` between the spawn and the call skipped even that.
/// This asserts the fix's actual contract — no ticker thread survives the call —
/// by counting live threads, rather than trying to catch the downstream symptom.
/// A symptom test was tried first and rejected: a stray bump only does damage if
/// it lands inside a later execution's call window, and with the fast fixtures
/// available here it lands between runs instead, so the test passed with the bug
/// deliberately reintroduced. Thread count fails against that same reintroduction.
#[test]
fn epoch_ticker_does_not_outlive_the_execution() {
fn live_threads() -> usize {
std::fs::read_dir("/proc/self/task")
.expect("Linux /proc/self/task — this test is Linux-only, as is CI")
.count()
}
// A long budget: with the ticker detached, these threads linger for a full
// minute each, so the count after the loop is unambiguous.
let executor = ScriptExecutor::try_new()
.expect("baseline engine")
.with_epoch_deadline_ticks(60);
let mut serializer = MemorySerializer::new();
serializer.set_context(ContextType::EntityCreate, EntityType::Transaction);
let input = serializer.finalize(4096);
// One warm-up run first: wasmtime brings up a parallel-compilation pool
// sized to the host's cores on first use, and counting that as a leak would
// make this test a core-count assertion.
executor
.execute(TEST_WASM, &input, Some(4096))
.expect("warm-up execution failed");
let before = live_threads();
for _ in 0..8 {
.expect("execution failed");
let after = live_threads();
assert!(
after <= before + 1,
"ticker threads outlived their executions: {before} live threads before 8 runs, \
{after} after — a detached ticker keeps bumping the shared engine epoch"
);