Cache compiled Lua scripts between executions #127

Closed
opened 2026-08-05 20:09:52 +00:00 by hugo · 1 comment
Owner

Summary

RuDS currently creates a fresh sandboxed Lua state and calls lua.load(source).exec() for every script invocation. This recompiles the same source for every custom macro occurrence and for every enabled Blogmark transformer on every capture. Syntax validation also compiles the source but discards the result.

Add a small process-local cache of compiled Lua 5.4 binary chunks in the shared scripting runtime so every execution path benefits without changing macro or transformer call sites.

Minimal design

  • Cache compiled bytecode, not live Lua states or Function handles. Each invocation must still create a fresh sandbox, install its current project-scoped host API, hooks, memory limit, cancellation state, output sinks, and then execute the cached chunk. This preserves isolation and allows concurrent use.
  • Put the cache in crates/bds-core/src/scripting/mod.rs, behind the existing validate / execute_many_with_host path. render_script_macro and run_transforms should continue using the shared executor unchanged.
  • Key entries by the exact Lua source contents. Source changes therefore miss automatically; entrypoint, project, host, arguments, and execution kind do not affect compilation and must not fragment the cache.
  • Compile text with Chunk::into_function(), retain debug information with Function::dump(false), and load cache hits explicitly as ChunkMode::Binary before executing the chunk and resolving the configured entrypoint.
  • Keep the cache in memory only and bounded by a small fixed entry or byte limit using the standard library. Simple eviction is sufficient; do not add a cache dependency or persistent bytecode format. Lua bytecode is runtime/architecture-specific and cached files would create compatibility and trust problems.
  • Failed compilation must never enter the cache. A poisoned cache mutex must not make scripting unavailable.
  • Route syntax validation through the same compile/cache helper so checking or publishing a script can warm the cache for later execution during the same process lifetime.

Implementation plan

  1. Extract the existing text compilation into an internal helper returning shared binary chunk bytes, backed by a bounded Mutex/OnceLock cache.
  2. Make validate call that helper. Make execute_many_with_host obtain the cached bytes, create its normal fresh sandbox, install the host API and hook, then load/execute the binary chunk.
  3. Add focused unit tests in the scripting module for cache hit, source-change miss, invalid-source exclusion, eviction, equivalent execution, and fresh-state isolation between repeated calls.
  4. Run cargo fmt --all -- --check, warnings-denied workspace Clippy, and cargo test --workspace with loopback permission as required by AGENTS.md.

Acceptance criteria

  • Repeated execution of byte-identical source compiles it once per RuDS process and reuses the cached bytecode afterward, including custom macro expansion and repeated Blogmark imports.
  • Editing source causes a cache miss without an explicit invalidation call.
  • Every invocation retains the current sandbox libraries, 32 MiB memory limit, host API, cancellation hook, macro timeout, output/progress/toast isolation, and fresh Lua globals.
  • Script results and errors remain behaviorally equivalent, including useful source/line diagnostics from cached chunks.
  • The cache is thread-safe, bounded, non-persistent, and adds no dependency.
  • Tests prove cache reuse and prove that globals/state cannot leak from one invocation to the next.

Out of scope

  • Pooling or reusing Lua VMs.
  • Persisting bytecode across application launches.
  • Caching macro output or transformer results.
  • Adding UI or configuration for cache sizing before measurements show it is needed.
## Summary RuDS currently creates a fresh sandboxed Lua state and calls `lua.load(source).exec()` for every script invocation. This recompiles the same source for every custom macro occurrence and for every enabled Blogmark transformer on every capture. Syntax validation also compiles the source but discards the result. Add a small process-local cache of compiled Lua 5.4 binary chunks in the shared scripting runtime so every execution path benefits without changing macro or transformer call sites. ## Minimal design - Cache compiled bytecode, not live `Lua` states or `Function` handles. Each invocation must still create a fresh sandbox, install its current project-scoped host API, hooks, memory limit, cancellation state, output sinks, and then execute the cached chunk. This preserves isolation and allows concurrent use. - Put the cache in `crates/bds-core/src/scripting/mod.rs`, behind the existing `validate` / `execute_many_with_host` path. `render_script_macro` and `run_transforms` should continue using the shared executor unchanged. - Key entries by the exact Lua source contents. Source changes therefore miss automatically; entrypoint, project, host, arguments, and execution kind do not affect compilation and must not fragment the cache. - Compile text with `Chunk::into_function()`, retain debug information with `Function::dump(false)`, and load cache hits explicitly as `ChunkMode::Binary` before executing the chunk and resolving the configured entrypoint. - Keep the cache in memory only and bounded by a small fixed entry or byte limit using the standard library. Simple eviction is sufficient; do not add a cache dependency or persistent bytecode format. Lua bytecode is runtime/architecture-specific and cached files would create compatibility and trust problems. - Failed compilation must never enter the cache. A poisoned cache mutex must not make scripting unavailable. - Route syntax validation through the same compile/cache helper so checking or publishing a script can warm the cache for later execution during the same process lifetime. ## Implementation plan 1. Extract the existing text compilation into an internal helper returning shared binary chunk bytes, backed by a bounded `Mutex`/`OnceLock` cache. 2. Make `validate` call that helper. Make `execute_many_with_host` obtain the cached bytes, create its normal fresh sandbox, install the host API and hook, then load/execute the binary chunk. 3. Add focused unit tests in the scripting module for cache hit, source-change miss, invalid-source exclusion, eviction, equivalent execution, and fresh-state isolation between repeated calls. 4. Run `cargo fmt --all -- --check`, warnings-denied workspace Clippy, and `cargo test --workspace` with loopback permission as required by `AGENTS.md`. ## Acceptance criteria - Repeated execution of byte-identical source compiles it once per RuDS process and reuses the cached bytecode afterward, including custom macro expansion and repeated Blogmark imports. - Editing source causes a cache miss without an explicit invalidation call. - Every invocation retains the current sandbox libraries, 32 MiB memory limit, host API, cancellation hook, macro timeout, output/progress/toast isolation, and fresh Lua globals. - Script results and errors remain behaviorally equivalent, including useful source/line diagnostics from cached chunks. - The cache is thread-safe, bounded, non-persistent, and adds no dependency. - Tests prove cache reuse and prove that globals/state cannot leak from one invocation to the next. ## Out of scope - Pooling or reusing Lua VMs. - Persisting bytecode across application launches. - Caching macro output or transformer results. - Adding UI or configuration for cache sizing before measurements show it is needed.
hugo added the enhancement label 2026-08-05 20:09:52 +00:00
Author
Owner

Implemented in 87a1610. The shared scripting runtime now stores Lua 5.4 bytecode in a dependency-free, process-local 32-entry FIFO cache keyed only by exact source. Validation and execution share the helper; cache misses compile with into_function() and dump(false), while every execution still creates the existing fresh 32 MiB sandbox, installs the current host/output/progress/toast API and cancellation/timeout hook, then loads the cached chunk explicitly as binary. Failed compilation is excluded and poisoned locks recover. Macro and Blogmark transformer call sites remain unchanged. Added red/green tests for exact hits, edited-source misses, failure exclusion, eviction, concurrent compile-once behavior, poison recovery, validation warming, equivalent results, fresh globals, and source-line errors; updated Allium and README. Verified against bDS2 behavior and current mlua 0.12 APIs with Allium check/analyse, fmt, warnings-denied workspace Clippy, the full workspace suite (575 core passed; one existing ignored real-model test), workspace build, and release macOS bundle.

Implemented in 87a1610. The shared scripting runtime now stores Lua 5.4 bytecode in a dependency-free, process-local 32-entry FIFO cache keyed only by exact source. Validation and execution share the helper; cache misses compile with into_function() and dump(false), while every execution still creates the existing fresh 32 MiB sandbox, installs the current host/output/progress/toast API and cancellation/timeout hook, then loads the cached chunk explicitly as binary. Failed compilation is excluded and poisoned locks recover. Macro and Blogmark transformer call sites remain unchanged. Added red/green tests for exact hits, edited-source misses, failure exclusion, eviction, concurrent compile-once behavior, poison recovery, validation warming, equivalent results, fresh globals, and source-line errors; updated Allium and README. Verified against bDS2 behavior and current mlua 0.12 APIs with Allium check/analyse, fmt, warnings-denied workspace Clippy, the full workspace suite (575 core passed; one existing ignored real-model test), workspace build, and release macOS bundle.
hugo closed this issue 2026-08-07 13:51:08 +00:00
Sign in to join this conversation.