perf(runtime): only reinstall callbacks when their declarations change - #416
Merged
Merged
Conversation
Prior to this commit, mise.toml asked for rust "1.98", which rustup had installed as 1.98.0, while rust-toolchain.toml and the workspace rust-version both require 1.98.1. Every cargo invocation through the mise shim failed with "rustc 1.98.0 is not supported" until the toolchain was overridden by hand. This commit pins mise to the same 1.98.1 so all three agree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Prior to this commit, the guest ran its callback setup script on every `execute` unless the host flagged a fresh instance with no callbacks. The script compiles a few hundred lines of Python (the `invoke` coroutine, the namespace classes, a wrapper per callback) and cost about 2.5 ms per execution: `session_execution/pass` measured 2.7 ms, of which running `pass` was well under 0.2 ms. Persistent sessions and every sandbox with callbacks paid it on each call; the JavaScript host never set the flag, so it paid on every call too. This commit makes `setup_callbacks` idempotent. The guest remembers the declarations it installed (as the JSON handed to the script) and only runs the script when the host's current declarations differ. Pre-initialization installs the empty set, and that record is part of the snapshot, so fresh instances without callbacks skip the script as before without needing the host's hint. `clear_state` and `restore_state` forget the record so the next execution reinstalls, since restored globals can shadow the wrappers. The `reuse-empty-callbacks` execution option stays in the WIT for compatibility with existing hosts; the guest ignores it. `session_execution/pass`: 2.71 ms -> 0.18 ms. Stateless executions with callbacks are unchanged: each fresh instance starts from the snapshot's empty set, so they still install once per instance. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
🌐 Demo preview: https://perf-guest-callback-setup-ca.eryx-bvy.pages.dev |
sd2k
marked this pull request as ready for review
September 11, 2026 08:28
Merged
sd2k
added a commit
that referenced
this pull request
Sep 11, 2026
A fresh sandbox starts from the pre-init snapshot, which was taken with the empty callback set, so its first execute() always reinstalled the callback wrappers (~3.4 ms of Python for three callbacks) even after #416 taught the guest to skip unchanged sets. Let the declarations be supplied at pre-init time: PreInitOptions / pre_initialize_with_options() in eryx-runtime (the list-callbacks stub answers with them and a no-op execute installs them into the snapshot), eryx::preinit::callback_declaration(), `eryx-precompile --callbacks <json>`, and SandboxFactory(callbacks=...) in pyeryx, whose sandboxes and sessions register those callbacks by default. The host now presents its callbacks to the guest sorted by name so registration order cannot cause a mismatch with the baked set. stateless_execution/pass with three callbacks: 4.70 ms -> 1.30 ms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The guest ran its callback setup script on every
executeunless the host flagged a fresh instance with no callbacks. That script compiles a few hundred lines of Python (invoke,list_callbacks, the_EryxNamespace/_EryxCallbackLeafclasses, one wrapper per callback) and costs ~2.5 ms per execution —session_execution/passmeasured 2.7 ms while runningpassitself is well under 0.2 ms. Every persistent session paid it on each call (sessions pinreuse_empty_callbacks: false), as did every sandbox with callbacks, and the JavaScript host never sets the flag so it paid on every call too.This makes
setup_callbacksidempotent: the guest keeps the declarations it installed (the JSON handed to the script, in a static that is part of the pre-init snapshot) and only runs the script when the host's current declarations differ.clear_state/restore_stateinvalidate the record so the next execution reinstalls.Companion to #411 (host side); independent of it.
Changes
crates/eryx-wasm-runtime/src/python.rs:INSTALLED_CALLBACKSfingerprint;setup_callbacksearly-returns on a match; invalidation inclear_stateandrestore_state; theCALLBACKS_PRE_INITIALIZEDflag is gone (pre-init'ssetup_callbacks(&[])now records the empty set instead).crates/eryx-wasm-runtime/src/lib.rs:initialize_callbacksno longer consultsreuse-empty-callbacks; the WIT field stays for host compatibility and is documented as advisory.crates/eryx-runtime/prebuilt/liberyx_runtime.so.zstrebuilt from the new guest (mise run build-eryx-runtime).crates/eryx/tests/callback_setup_cache.rs.Behaviour note
A session that redefines or deletes a callback wrapper in user code now keeps that change until the callback set changes (previously the next execute silently reinstalled the wrappers over it).
clear_statestill restores everything.Measurements (7950X, release,
cargo bench --package eryx --features embedded)session_execution/passstateless_execution/pass(3 callbacks)Follow-up for stateless-with-callbacks: either key #411's warm pool by callback fingerprint and pre-run
passon the warm instance, or letSandboxFactory/preinit bake the callback declarations into the snapshot so fresh instances match immediately.Testing
cargo nextest run --workspace --features embedded: 629 passed (new tests: unchanged set across executions, changed set picked up in both directions, callbacks surviveclear_stateand snapshot/restore, stateless sandbox with callbacks repeatedly). Existingtest_session_refreshes_empty_callbacks_after_callback_executionstill passes.cargo clippy --workspace --all-targets --all-features -- -D warningsclean.Checklist
🤖 Generated with Claude Code