Skip to content

widgets: MAKEPAD_SPLASH_BUDGET_MS overrides a Splash entry's 64 ms budget - #109

Open
ymote wants to merge 1 commit into
mainfrom
fix/splash-budget-override
Open

ymote wants to merge 1 commit into
mainfrom
fix/splash-budget-override

Conversation

@ymote

@ymote ymote commented Oct 8, 2026

Copy link
Copy Markdown

Why

Every entry into a Splash isolate gets a 64 ms wall-clock budget (with_splash_budget). Wall-clock time counts every millisecond the entry spends waiting for the CPU or a lock, as well as script work. A test binary running ~1,000 tests in one process is where that breaks down.

OctoSense's shell suite was measured on a two-socket 72-vCPU Linux host (EC2 c5d.18xlarge), with each entry's wall time, on-CPU time and LiveId-interner lock waits instrumented:

Case Photos preview entry
Alone 6 ms (6 ms on CPU, 0.4 ms of interner waits)
Full suite, 18–72 test threads 65–1,100 ms

Two causes:

  • The process-wide LiveIdInterner mutex. The tokenizer takes it ~12,700 times per Photos entry. Under load, slow entries wait 90–230 ms on it, mostly as futex time in the kernel (sys 150–350 ms per entry).
  • Page faults blocked in lock_mm_and_find_vma. They wait while other threads in the same process map, unmap or fork.

The tests then fail with script time budget exceeded, although they assert behaviour that has nothing to do with time. OctoSense#357 already hit this and trimmed a test "under concurrent CI load". The Mac passes only because it has one socket and fewer threads.

A read-mostly interner (an RwLock with a read fast path) was also tried. It cut the suite from 15 s to 9 s and context switches from thousands to tens, but it did not stop the failures, so it is not part of this PR.

What

MAKEPAD_SPLASH_BUDGET_MS sets the per-entry budget, like the crate's other MAKEPAD_* switches. It is read once. An unset, empty, zero or unparsable value keeps 64 ms, so nothing changes unless a process opts in. The per-app instruction budget (splash_policy) is separate and unchanged.

OctoSense sets it only for its test step, and carries this change as a stacked runtime patch until the next makepad sweep, so main does not move for it.

Checks

  • cargo check -p makepad-widgets: no warnings in makepad-widgets (Linux x86_64, on this branch).
  • cargo test -p makepad-widgets --lib (the existing tests): running on the Linux host. Results will be posted here.
  • OctoSense shell suite with the override, on the Linux host: results will be posted here.

🤖 Generated with Claude Code

…dget

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 <noreply@anthropic.com>
@ymote

ymote commented Oct 8, 2026

Copy link
Copy Markdown
Author

Check results (Linux x86_64, EC2 c5d.18xlarge):

  • cargo test -p makepad-widgets --lib: 267 passed, 6 failed. The same 6 fail on main (32d6415) without this change, both one at a time and in the full suite, so they are existing Linux failures, unrelated to this PR:
    • desktop_style::tests::mobile_typefaces_keep_symbol_fallbacks_across_appearances
    • grid::tests::oversized_child_is_not_cell_clipped_and_only_obeys_the_grid_clip
    • grid::tests::yielding_child_resumes_in_the_same_cell_and_nested_grid_draws
    • menu_bar::tests::text_editor_focus_has_shortcut_precedence
    • widget_tree::tests::test_observe_and_find_single_node
    • widget_tree::tests::test_property_patch_no_structural_rebuild
  • OctoSense Home's test command, with this change as a stacked runtime patch and MAKEPAD_SPLASH_BUDGET_MS=10000, at the default 72 test threads: 5 of 5 runs green (1,449 tests, ~42 s each).
    • A control run without the variable failed the two Photos preview tests, as before.
    • Run times match with and without the variable, so no test depends on the budget running out.

OctoSense carries this change as tools/runtime-patches/makepad-splash-budget.patch in OctoSense-org/OctoSense#376, until the next sweep.

@ymote

ymote commented Oct 8, 2026

Copy link
Copy Markdown
Author

OctoSense-org/OctoSense#376 merged (31a34f7b): OctoSense now carries this change as stacked runtime patch makepad-splash-budget.patch. Its phone test step sets MAKEPAD_SPLASH_BUDGET_MS=10000, and ci-local --offload runs Home's tests on the Linux host. The offloaded gate went from 32m21s to 12m42s. This PR stays open until the next makepad sweep folds it into main. When that sweep lands, the stacked patch comes out.

@sansanyixyz331

Copy link
Copy Markdown

A data point from a real application rather than the test suite — in case it helps with the default,
or with the note that a host can set it for its own process.

A mini-app's Splash body here is 59,625 bytes — one entry, evaluated once when the page opens.

machine [SPLASH] eval … view= script time budget exceeded
2024 i5-14600K (14 cores / 20 threads) true 0
2015 i5-6500 (4 threads, Intel HD 530) false 12

On the slower machine the whole canvas stays blank, and the log carries:

[E] octoscript-widgets/src/design.rs:177  - script time budget exceeded (platform/script/src/vm.rs:1046)
[I] makepad/widgets/src/splash.rs:341     - splash: ...: script time budget exceeded
[I] makepad/widgets/src/splash.rs:414     - [SPLASH] eval: 59625 bytes ... view=false

With the budget raised to 3000 ms, the same binary and the same package on the same slow machine give
view=true, 0 budget lines, and the view renders.

Two things that stood out there, in case they bear on the choice of default:

  1. This entry runs once at startup, not per frame — a slow first evaluate is not a runaway that the
    cap needs to end.
  2. The budget is wall-clock, so it also counts a cold page cache and the first read of the font and
    icon files; on this machine most of the overrun was that rather than script work.

The override covers it either way. We are carrying the raised value in a local build until then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants