Skip to content

Add optional execution-cancellation breakpoint - #1900

Open
gwenaskell wants to merge 9 commits into
mainfrom
yoenn.burban/vrl-execution-timeout-breakpoint
Open

Add optional execution-cancellation breakpoint#1900
gwenaskell wants to merge 9 commits into
mainfrom
yoenn.burban/vrl-execution-timeout-breakpoint

Conversation

@gwenaskell

@gwenaskell gwenaskell commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds an execution_cancellation Cargo feature (off by default) that lets an embedder abort a running VRL program from another thread.
  • The embedder owns an Arc<AtomicBool> and registers it via Runtime::set_cancellation_flag (or RuntimeState::set_cancellation_flag). Flip it to true from anywhere — your own timeout timer, a client disconnect, a shutdown signal — and the program panics as soon as it's observed, rather than running to completion.
  • The check lives in Context::cancel_breakpoint, called once per Expr::resolve — the single dispatch point every expression in a program flows through. This also covers VRL's only loop construct: stdlib iteration functions (for_each, map_values, filter, reduce, ...) invoke their closure body through this same path once per element, so a script looping over a large/attacker-controlled collection is caught too.
  • No internal deadline/clock tracking: it's a plain AtomicBool::load(Relaxed), cheap enough to check on every expression resolution with no throttling needed. Timing policy (how long is "too long") is entirely up to the embedder.
  • With the feature disabled (the default), Context::cancel_breakpoint compiles to an empty #[inline(always)] function — a true no-op.

Why panic instead of a Terminate error

This is meant as a hard safety net against runaway scripts, not a recoverable VRL-level error like abort. Callers that want to turn this into a graceful failure can wrap Runtime::resolve in std::panic::catch_unwind.

Test plan

  • cargo test --features default --lib — 1851 tests pass, unchanged.
  • cargo test --features default,execution_cancellation --lib — 1855 tests pass (4 new).
  • cargo clippy --features default --lib -- -D warnings and cargo clippy --features default,execution_cancellation --lib -- -D warnings — clean.
  • Unit tests for the flag-check logic (compiler::state::execution_cancellation_tests).
  • End-to-end tests compiling and running a real for_each loop (compiler::runtime::execution_cancellation_tests): one cancelled before it starts, one cancelled from a second thread mid-loop over 500,000 elements — both panic as expected. Ran the mid-execution test 20x to check for flakiness; stable.

@gwenaskell gwenaskell changed the title Add optional wall-clock execution timeout (breakpoint hook) Add optional execution-cancellation breakpoint Aug 21, 2026
Adds an execution_timeout feature that lets embedders bound how long a VRL
program is allowed to run. When enabled and a timeout is set via
Runtime::set_timeout, every expression resolution checks a deadline
(throttled to once every 1024 calls) and panics if it has passed. This is
a no-op when the feature is disabled, which is the default.

The check is inserted at the single point every expression in a program
flows through (Expr::resolve), so it also covers the interpreter's only
looping construct: stdlib iteration functions (for_each, map_values,
filter, etc.) re-enter this dispatch once per closure invocation.
… timeout

Renames execution_timeout to execution_cancellation. Instead of the
compiler tracking an internal deadline, Runtime::set_cancellation_flag now
takes an Arc<AtomicBool> the embedder owns and can flip from any thread
(e.g. from their own timer, on client disconnect, on shutdown). The
breakpoint hook is renamed to Context::cancel_breakpoint to reflect that
it only checks this flag. Dropping the internal deadline also removes the
need to throttle clock reads: an atomic load is cheap enough to check on
every expression resolution.
@gwenaskell
gwenaskell force-pushed the yoenn.burban/vrl-execution-timeout-breakpoint branch from 6a78dca to 8be0191 Compare August 21, 2026 13:49
Reorders the std::sync imports to match rustfmt's grouping, and drops the
unnecessary raw-string hashes flagged by clippy::pedantic's
needless_raw_string_hashes now that CI runs against the 1.95 toolchain.
Comment thread changelog.d/1900.feature.md Outdated
Comment thread src/compiler/expression.rs Outdated
Puts #[cfg(feature = "execution_cancellation")] on the call site in
Expr::resolve instead of relying on an empty no-op impl of
Context::cancel_breakpoint to absorb the disabled case. Drops that
no-op impl now that it's unreachable.

Also drops the changelog fragment: this is an opt-in, embedder-only
Cargo feature with no user-facing effect on VRL scripts themselves.
@gwenaskell gwenaskell added the no-changelog Changes in this PR do not need user-facing explanations in the release changelog label Aug 24, 2026
@gwenaskell
gwenaskell marked this pull request as ready for review August 24, 2026 09:32
@gwenaskell
gwenaskell requested a review from a team as a code owner August 24, 2026 09:32
Comment thread src/compiler/runtime.rs Outdated
It only stayed race-free by relying on debug-profile interpreter speed
outpacing a fixed 5ms sleep — CI runs cargo test without --release, so
this held today, but it's a timing assumption rather than a guarantee,
and AtomicBool cross-thread visibility isn't logic this crate owns.
for_each_loop_panics_when_already_cancelled already covers the actual
wiring (check_cancellation is consulted and panics) deterministically.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9c9532cdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/compiler/expression.rs Outdated
Comment thread src/compiler/expression.rs Outdated
gwenaskell and others added 3 commits August 24, 2026 15:02
check_cancellation now raises std::panic::panic_any(Cancelled) instead
of assert!'s string payload, so a caller wrapping Runtime::resolve in
catch_unwind can downcast to distinguish an intentional cancellation
from any other panic. Also trims the set_cancellation_flag/
cancel_breakpoint doc comments down to what a cold reader needs.
Addresses two review findings on the cancel_breakpoint call site:
- the flag is only checked between expressions, so a single
  long-running or blocking stdlib call still runs to completion
  before the next check can fire
- a RuntimeState caught mid for_each-closure cancellation shouldn't
  be reused, since the panic can unwind before closure::Runner's
  cleanup restores a shadowed outer variable

Both are inherent to checking cancellation at expression-dispatch
granularity rather than something this PR fixes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog Changes in this PR do not need user-facing explanations in the release changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants