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.

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions