feat(runtime): per-script execution timeout overriding the global default (#9)

Spec promised a per-script timeout but only the global ScriptExecutionTimeoutSeconds
existed. Add nullable TemplateScript.ExecutionTimeoutSeconds threaded through EF +
flattening (ResolvedScript) to ScriptExecutionActor/AlarmExecutionActor, which use
perScript ?? global for the execution CTS. Includes the EF migration for the new column.
This commit is contained in:
Joseph Doherty
2026-06-15 14:40:38 -04:00
parent 00304a26e6
commit 3edef09f51
22 changed files with 2094 additions and 17 deletions
@@ -47,6 +47,7 @@ public class ScriptExecutionActor : ReceiveActor
/// <param name="healthCollector">Optional health collector for recording execution metrics.</param>
/// <param name="serviceProvider">Optional DI service provider for script execution services.</param>
/// <param name="parentExecutionId">ExecutionId of the spawning inbound-API execution for audit correlation; null for normal runs.</param>
/// <param name="executionTimeoutSeconds">M2.5 (#9): per-script execution timeout in seconds. Null or non-positive falls back to the global <see cref="SiteRuntimeOptions.ScriptExecutionTimeoutSeconds"/>.</param>
public ScriptExecutionActor(
string scriptName,
string instanceName,
@@ -65,7 +66,10 @@ public class ScriptExecutionActor : ReceiveActor
// Audit Log #23 (ParentExecutionId): the spawning execution's
// ExecutionId for an inbound-API-routed call. Null for normal
// (tag-change / timer) runs and nested Script.Call invocations.
Guid? parentExecutionId = null)
Guid? parentExecutionId = null,
// M2.5 (#9): per-script execution timeout override (seconds). Null or
// non-positive falls back to the global ScriptExecutionTimeoutSeconds.
int? executionTimeoutSeconds = null)
{
// Immediately begin execution
var self = Self;
@@ -75,7 +79,7 @@ public class ScriptExecutionActor : ReceiveActor
scriptName, instanceName, compiledScript, parameters, callDepth,
instanceActor, sharedScriptLibrary, options, replyTo, correlationId,
self, parent, logger, scope, healthCollector, serviceProvider,
parentExecutionId);
parentExecutionId, executionTimeoutSeconds);
}
private static void ExecuteScript(
@@ -95,9 +99,15 @@ public class ScriptExecutionActor : ReceiveActor
Commons.Types.Scripts.ScriptScope scope,
ISiteHealthCollector? healthCollector,
IServiceProvider? serviceProvider,
Guid? parentExecutionId)
Guid? parentExecutionId,
int? executionTimeoutSeconds)
{
var timeout = TimeSpan.FromSeconds(options.ScriptExecutionTimeoutSeconds);
// M2.5 (#9): per-script timeout overrides the global default. A null or
// non-positive per-script value (≤ 0) falls back to the global.
var timeout = TimeSpan.FromSeconds(
executionTimeoutSeconds is { } perScript && perScript > 0
? perScript
: options.ScriptExecutionTimeoutSeconds);
// SiteRuntime-009: run the script body on the dedicated script-execution
// scheduler, not the shared .NET thread pool, so blocking script I/O cannot