From e3b9271cc01cc33fa9a44a47198d0eef86442e26 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:15:03 -0700 Subject: [PATCH] widgets: MAKEPAD_SPLASH_BUDGET_MS overrides a Splash entry's 64 ms budget The budget is wall-clock time, so it counts time an entry spends waiting for the CPU or a lock, not just the script. A test binary running ~1,000 tests in one process is where that breaks down. On a two-socket 72-vCPU Linux host, OctoSense's shell suite at 18-72 test threads turns a 6 ms Photos entry into 65-1,100 ms. Two causes were measured: - The process-wide LiveId interner mutex, which the tokenizer takes ~12,700 times per entry: 90-230 ms of lock waits under load, mostly futex time in the kernel. - Page faults stalled behind other threads' memory-map changes. Tests then fail with "script time budget exceeded" while asserting behaviour that has nothing to do with time. The variable is read once. An unset, empty, zero or unparsable value keeps 64 ms, so nothing changes unless a process opts in. Co-Authored-By: Claude Opus 5.5 --- widgets/src/widget_async.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs index d2f22abeb1..b4fb43e4dd 100644 --- a/widgets/src/widget_async.rs +++ b/widgets/src/widget_async.rs @@ -451,14 +451,31 @@ pub fn contain_isolate_panic(what: &str, f: impl FnOnce()) -> bool { } } +/// How long one entry into a Splash isolate may run: 64 ms, unless +/// `MAKEPAD_SPLASH_BUDGET_MS` says otherwise. The budget is wall-clock time, +/// so it also counts time the entry spends waiting for the CPU or a lock. A +/// test binary running a thousand tests in one process is the case for the +/// override: there the wall clock says little about the script. +fn splash_entry_budget() -> std::time::Duration { + static BUDGET: std::sync::OnceLock = std::sync::OnceLock::new(); + *BUDGET.get_or_init(|| { + let ms = std::env::var("MAKEPAD_SPLASH_BUDGET_MS") + .ok() + .and_then(|value| value.trim().parse::().ok()) + .filter(|&ms| ms > 0) + .unwrap_or(64); + std::time::Duration::from_millis(ms) + }) +} + /// A Splash isolate runs untrusted-ish user script on the UI thread; cap how long any /// single entry into it may run. fn with_splash_budget(vm: &mut ScriptVm, f: impl FnOnce(&mut ScriptVm) -> R) -> R { - let old_budget = vm.bx.run_budget.replace(ScriptRunBudget::from_durations( - std::time::Duration::from_millis(64), - std::time::Duration::from_millis(64), - 512, - )); + let budget = splash_entry_budget(); + let old_budget = vm + .bx + .run_budget + .replace(ScriptRunBudget::from_durations(budget, budget, 512)); let out = f(vm); vm.bx.run_budget = old_budget; out