Skip to content

Allow hosts to assign the V8 default stack size #726

Description

@KarnamV

Summary

V8ScriptEngine.MaxRuntimeStackUsage is the only supported way to increase V8's JS stack limit (deeper recursion), and it works — but on current V8 it aborts the process at the next GC or on dispose, and no managed-side mitigation avoids it.

Environment

  • Microsoft.ClearScript.V8 7.5.1
  • Microsoft.ClearScript.V8.Native.win-x64 7.5.1 (bundled V8 14.7.173.23)
  • .NET 10 (net10.0), win-x64

Repro (minimal, no external JS libs)

var engine = new V8ScriptEngine();
engine.MaxRuntimeStackUsage = (UIntPtr)(2 * 1024 * 1024);   // remove this line -> no crash
engine.Execute("var a=[]; for (var k=0;k<200000;k++){ a.push({x:k}); } a=null;");
engine.CollectGarbage(true);   // aborts: "# Check failed: isolate_->IsOnCentralStack()."
engine.Dispose();

Exit code 0x80000003 (native int 3). Removing only the MaxRuntimeStackUsage line makes it exit cleanly — it is the sole trigger.

It genuinely increases depth (so it's a needed capability)

Measured max recursion depth with an 8 MB cap on a 16 MB thread: 104,827 frames, vs the V8 default of ~12,480. Depth is otherwise independent of the OS thread stack size (V8 uses its own --stack-size, default ~1 MB), so this cap is the only lever that raises it.

No managed workaround avoids the abort

All of these still abort at the GC/dispose with the same IsOnCentralStack CHECK:

  • Setting MaxRuntimeStackUsage = 0 before the GC/dispose
  • The reset + a dummy Execute (fresh execution scope) before the GC
  • V8GlobalFlags.DisableBackgroundWork | DisableJITCompilation
  • Setting the limit at the V8Runtime level instead of the engine

Root cause (analysis)

MaxRuntimeStackUsage is the only path that calls v8::Isolate::SetStackLimit() (V8IsolateImpl::EnterExecutionScope), overriding V8's own limit with an absolute address. On V8 14.7 (WebAssembly enabled) V8 asserts CHECK(isolate_->IsOnCentralStack()) during the GC conservative stack scan (and in Isolate::Throw); the override leaves the live SP outside V8's recorded central-stack window, so the CHECK fails and V8 aborts. A full-dump native stack shows: V8Context_CollectGarbage -> V8 GC stack scan -> CHECK -> DebugBreak (int 3). Note ClearScript's exit path resets the limit to a sentinel (s_pMinStackLimit, addr 0x8) rather than restoring V8's real limit, so the isolate stays poisoned even after resetting.

Requests

  1. Fix MaxRuntimeStackUsage on V8 >= 13/14 (keep the stack bookkeeping consistent with V8's central-stack invariant), or document it as unsupported there.
  2. Expose V8's --stack-size (e.g. V8RuntimeConstraints.MaxStackSize) as the safe equivalent — it raises the limit through V8's own init path, so overflow stays a catchable RangeError with no abort.

Happy to share the full minimal repro project and the captured crash dump / symbolized analysis if useful.

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions