Skip to content

test(risor,starlark): regression test for Eval cancellation timing#127

Merged
robbyt merged 1 commit into
mainfrom
tests/issue-125-eval-cancellation
May 14, 2026
Merged

test(risor,starlark): regression test for Eval cancellation timing#127
robbyt merged 1 commit into
mainfrom
tests/issue-125-eval-cancellation

Conversation

@robbyt
Copy link
Copy Markdown
Owner

@robbyt robbyt commented May 14, 2026

Summary

PR #81 added context.AfterFunc(ctx, thread.Cancel) to Starlark's exec() to fix a goroutine leak, but no test asserted that cancellation actually halts execution within a bounded time. Same gap existed for Risor — the Risor v2 VM checks ctx.Done() at DefaultContextCheckInterval (1000 instructions) and via a background goroutine, but nothing locked the behavior in. Extism is already covered by the execHelper cancellation cases added in PR #121.

Adds TestEval_CancellationHaltsExecution to both Risor and Starlark evaluator test files. The script body is a long-running loop sized so natural completion would far outrun the 2-second test deadline; only ctx cancellation can return Eval early.

Script bodies

Engine Long-running construct Why
Risor range(1e9).each(x => x) Risor v2 has no for/while statements (functional, uses .map/.filter/.each). The VM's periodic ctx-check inside .each's callable.Call propagates cancellation.
Starlark for i in range(1e12): pass inside def spin(): ... Lazy range + Python-style loop. The AfterFunc-registered thread.Cancel halts at the next instruction boundary.

Test shape

Both tests:

  1. Launch Eval in a goroutine.
  2. Sleep 50ms so the engine is observably mid-script.
  3. Cancel the context.
  4. Assert Eval returns within 2s with a cancellation-shaped error — errors.Is(context.Canceled) OR a "context canceled" / "cancel" substring (Starlark's thread.Cancel wraps the reason in an EvalError that doesn't unwrap to context.Canceled).

Stress test

Locally ran go test -race -count=20 -run TestEval_CancellationHaltsExecution ./engines/risor/evaluator/... ./engines/starlark/evaluator/... — 40 runs, zero flakes, ~2 seconds total wall time.

Out of scope

  • Eval-halts-on-deadline (context.WithTimeout) — same code path as cancel.
  • Extism — already covered.

Test plan

  • go test -race -count=1 ./... — full suite green
  • go test -race -count=20 on the two new tests — no flakes
  • go vet ./... — clean
  • CI

Closes #125

https://claude.ai/code/session_01C61VEAmjxSnX5Xhbab8NvL


Generated by Claude Code

Closes #125

PR #81 added `context.AfterFunc(ctx, thread.Cancel)` to Starlark's
exec() to fix a goroutine leak, but no test asserted that
**cancellation actually halts execution within a bounded time**. Same
gap existed for Risor — the Risor v2 VM checks ctx.Done() at
DefaultContextCheckInterval (1000 instructions) and via a background
goroutine, but nothing locked the behavior in. Extism is already
covered by the execHelper cancellation cases added in PR #121.

Add TestEval_CancellationHaltsExecution to both Risor and Starlark
evaluator test files. The script body is a long-running loop sized so
natural completion would far outrun the 2-second test deadline; only
ctx cancellation can return Eval early.

  - Risor: Risor v2 has no for/while statements (it's a functional
    language with .map/.filter/.each higher-order methods), so the
    long-running construct is `range(1e9).each(x => x)`. The VM's
    periodic ctx-check inside .each's callable.Call propagates
    cancellation.
  - Starlark: lazy `range(1e12)` with a Python-style for-loop. The
    AfterFunc-registered thread.Cancel halts at the next instruction
    boundary.

Both tests:
  1. Launch Eval in a goroutine.
  2. Sleep 50ms so the engine is observably mid-script.
  3. Cancel the context.
  4. Assert Eval returns within 2s with a cancellation-shaped error
     (errors.Is(context.Canceled) OR "context canceled" / "cancel"
     substring — Starlark's thread.Cancel wraps the reason in an
     EvalError that doesn't unwrap to context.Canceled).

Stress-tested locally with `-count=20 -race`: 40 runs, no flakes,
~2 seconds total wall time.

Out of scope: Eval-halts-on-deadline (context.WithTimeout); same
code path as cancel.
Copilot AI review requested due to automatic review settings May 14, 2026 00:38
@github-actions
Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds regression tests verifying that cancelling the context passed to Eval() halts a long-running script within 2 seconds for both the Risor and Starlark engines, locking in the cancellation behavior fixed in PR #81 and the periodic ctx-check in Risor v2.

Changes:

  • Add TestEval_CancellationHaltsExecution to the Starlark evaluator test file using a for i in range(1e12): pass loop.
  • Add TestEval_CancellationHaltsExecution to the Risor evaluator test file using range(1e9).each(x => x).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
engines/starlark/evaluator/evaluator_test.go New cancellation timing regression test for Starlark Eval.
engines/risor/evaluator/evaluator_test.go New cancellation timing regression test for Risor Eval.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sonarqubecloud
Copy link
Copy Markdown

@robbyt robbyt merged commit 0e79757 into main May 14, 2026
7 checks passed
@robbyt robbyt deleted the tests/issue-125-eval-cancellation branch May 14, 2026 00:40
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.

[tests] Risor/Starlark Eval cancellation halts execution within bounded time

3 participants