Skip to content
bizzehdee edited this page Jun 25, 2026 · 2 revisions

Resource Limits

DScript can enforce two independent limits on script execution to prevent runaway scripts from hanging the host process.

Wall-clock timeout

engine.SetTimeout(TimeSpan.FromMilliseconds(500));

Execution is cancelled if the script runs for longer than the specified wall-clock duration. The check is made every 1024 VM instructions to minimise overhead.

Instruction limit

engine.SetInstructionLimit(10_000_000);

Execution is cancelled after the VM dispatches the given number of bytecode instructions. This is deterministic — the same script always trips the limit at the same point regardless of CPU speed.

Combining both limits

engine.SetTimeout(TimeSpan.FromSeconds(2));
engine.SetInstructionLimit(50_000_000);

Both limits are active simultaneously. Execution stops at whichever is reached first.

ScriptTimeoutException

Both limits throw ScriptTimeoutException, which is a plain .NET exception not derived from JITException or ScriptException. This means script-level try/catch blocks cannot intercept it — it always propagates to the C# host.

try
{
    engine.SetTimeout(TimeSpan.FromMilliseconds(100));
    engine.Run(ScriptEngine.Compile("while(true) {}"));
}
catch (ScriptTimeoutException ex)
{
    Console.Error.WriteLine($"Timed out: {ex.Message}");
}

Call-stack depth

Deeply or infinitely recursive scripts are bounded by MaxCallStackDepth so they throw a catchable "Maximum call stack size exceeded" error rather than overflowing the native .NET stack — which is uncatchable and would crash the process.

engine.MaxCallStackDepth = 5000;   // default is 10000

Top-level execution runs on a large (256 MB) stack, so the default 10000 sits well below the real overflow point with room for the error to unwind. Tail calls are trampolined and do not count toward the depth, so tail-recursive loops are unbounded. The error is reported to the host (e.g. caught by the REPL, which keeps running); like DScript's other internal errors it is not catchable by a script-level try/catch.

Resetting limits

Call the setters again to update the limits, or pass TimeSpan.Zero / 0 to disable them:

engine.SetTimeout(TimeSpan.Zero);        // disable wall-clock limit
engine.SetInstructionLimit(0);           // disable instruction limit

Implementation notes

  • _instrCount is reset to zero at the start of each Run() call.
  • The instruction limit check happens on every instruction outside the script's try/catch dispatcher.
  • The wall-clock check samples DateTime.UtcNow only every 1024 instructions to avoid the cost of a system call on every dispatch.

Clone this wiki locally