Skip to content

StackGuard: probe stack every 8 levels to fix flaky StackOverflow - #5656

Merged
MaceWindu merged 1 commit into
masterfrom
fix/stackguard-probe-interval
Jun 26, 2026
Merged

MaceWindu merged 1 commit into
masterfrom
fix/stackguard-probe-interval

Conversation

@MaceWindu

Copy link
Copy Markdown
Contributor

Problem

The deep-recursion stack guard used by the expression / SQL visitors intermittently crashes CI with an uncatchable StackOverflowException (e.g. StackUseTests on the net462 / Linux legs) instead of recovering via its thread-hop mechanism.

Root cause

StackGuard.Enter probed RuntimeHelpers.TryEnsureSufficientExecutionStack() only every 64 recursion levels. Between two probes up to ~64 × per-level stack is consumed with no check. Captured at the hop point on x64 there are 3 frames per level (ExpressionVisitorBase.VisitExpressionVisitorUtils.VisitArgumentsExpressionVisitor.VisitMethodCall), depth 4737 before the first hop ⇒ ~220 bytes/level, so 64 levels ≈ ~14 KB.

TryEnsureSufficientExecutionStack only guarantees a fixed margin. On tighter configurations (net462 / Linux, larger frames) those ~14 KB can exceed the margin, so the stack overflows between probes — a hard StackOverflowException that crashes the process — rather than the intended graceful hop.

Fix

Probe every 8 levels (named StackProbeInterval constant), bounding the between-probe stack to ~2 KB — comfortably inside the guaranteed margin on every TFM. The probe is a cheap JIT intrinsic and only matters for deep recursion (shallow queries probe ~once), so the cost is negligible. Hop semantics are unchanged; it just hops slightly sooner.

Verification

StackUseTests hop tests pass locally on net10.0 — TestExpressionVisitorHops / TestSqlVisitorHops (0/1/2 → nested InsufficientExecutionStackException, 10 → completes) and TestPreserveExceptionOnHop. The hard StackOverflow only reproduces on the tight-margin legs (net10.0/x64 hops gracefully at depth 4737), so CI on net462 / Linux is the final confirmation.

🤖 Generated with Claude Code

The deep-recursion stack guard (used by the expression/SQL visitors)
only probed TryEnsureSufficientExecutionStack every 64 levels. Between
two probes up to ~64 x per-level stack is consumed with no check; at
~220 bytes/level (x64) that is ~14KB, which can exceed the fixed margin
TryEnsureSufficientExecutionStack guarantees on tighter configurations
(net462 / Linux, where frames are larger). When that happens the stack
overflows BETWEEN probes - an uncatchable StackOverflowException that
crashes the process - instead of the intended graceful thread hop. This
showed up as a flaky StackOverflow in StackUseTests on CI.

Probe every 8 levels (named StackProbeInterval const), bounding the
between-probe stack to ~2KB, comfortably inside the guaranteed margin on
every TFM. The probe is a cheap JIT intrinsic and only matters for deep
recursion (shallow queries probe ~once), so the cost is negligible. Hop
semantics are unchanged - it just hops slightly sooner; StackUseTests'
TestExpressionVisitorHops / TestSqlVisitorHops assertions (0/1/2 hops
throw nested InsufficientExecutionStackException, 10 completes) still
pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaceWindu MaceWindu added this to the 6.4.0 milestone Jun 25, 2026
@MaceWindu MaceWindu self-assigned this Jun 25, 2026
@MaceWindu

Copy link
Copy Markdown
Contributor Author

/azp run test-all

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MaceWindu MaceWindu moved this from Todo to In Review in PR Review Queue Jun 25, 2026
@linq2dbot

Copy link
Copy Markdown

Test baselines changed by this PR. Don't forget to merge/close baselines PR after this pr merged/closed.

@MaceWindu
MaceWindu marked this pull request as ready for review June 26, 2026 06:31
@MaceWindu
MaceWindu merged commit 9b33c07 into master Jun 26, 2026
53 of 57 checks passed
@MaceWindu
MaceWindu deleted the fix/stackguard-probe-interval branch June 26, 2026 06:32
@github-project-automation github-project-automation Bot moved this from In Review to Done in PR Review Queue Jun 26, 2026
@MaceWindu

Copy link
Copy Markdown
Contributor Author

📝 Release-notes draft

🤖 Auto-generated user-facing summary for this PR. Toggle the boxes to control how it ships; the text is regenerated when new commits land (the maintainer confirms every change).

  • Omit from release notes (exclude from both the wiki notes and the GitHub release highlights)
  • Include in the GitHub release highlights (the brief release-page notes)

Full release notes (wiki)

  • Fixed an uncatchable StackOverflowException that could crash the process on deeply nested / recursive queries instead of recovering gracefully. The deep-recursion stack guard now probes the remaining stack more frequently, so it reliably switches to its thread-hop fallback (or throws a catchable InsufficientExecutionStackException) before the stack is exhausted. (#5656)

GitHub release highlight (brief)

(none)


Generated from commit 81b0f9b.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants