perf(runtime): split trigger evals from the blocking pool; bounded, deadline-aware execution

This commit is contained in:
Joseph Doherty
2026-08-14 22:36:15 -04:00
parent 312216ff2b
commit c4fc1f8ecd
42 changed files with 3673 additions and 1251 deletions
@@ -403,13 +403,15 @@ public class ScriptActorTests : TestKit, IDisposable
"ExprFault", "Expression", "{\"expression\":\"true\",\"mode\":\"OnTrue\"}", null, expr);
try
{
actor.Tell(Change("A", "1")); // eval starts on the scheduler and BLOCKS → _evalInFlight = true
actor.Tell(Change("A", "1")); // eval starts off-thread and BLOCKS → _evalInFlight = true
AwaitAssert(() => Assert.Equal(1, EvalGate.Entries), TimeSpan.FromSeconds(10));
// #18 seam: the blocked evaluation is running on THIS class's injected
// scheduler — not the process-wide singleton — so a worker it strands can
// never starve another test class.
Assert.Equal(1, _scheduler.BusyThreadCount);
// WP3.1: the blocked evaluation no longer occupies a script-execution worker at
// all — it runs on the shared thread pool behind TriggerEvalGate, which is the
// whole point of finding #4. The blocking pool must be completely idle here; the
// pre-WP3.1 assertion was the opposite (BusyThreadCount == 1).
Assert.Equal(0, _scheduler.BusyThreadCount);
Assert.Equal(0, _scheduler.QueueDepth);
actor.Tell(Change("A", "2")); // coalesces → _evalPending = true
@@ -432,15 +434,24 @@ public class ScriptActorTests : TestKit, IDisposable
}
[Fact]
public void ExpressionTrigger_EvaluatesOnScriptSchedulerThread_AndStillFires()
public void ExpressionTrigger_EvaluatesOffTheBlockingScriptPool_AndStillFires()
{
// The expression is TRUE only when evaluated on a script-execution thread.
// Before P1 it ran synchronously on the actor's dispatcher thread (name is
// NOT "script-execution-*") → false → no fire. After P1 it runs on the
// script scheduler → true → fire.
// WP3.1 retarget of the former P1 assertion, whose sense is deliberately INVERTED.
//
// P1 moved trigger-expression evaluation off the actor's dispatcher thread and onto
// the dedicated script-execution scheduler, and this test asserted exactly that
// ("evaluated on a script-execution-* thread"). WP3.1 (finding #4) proved that
// destination wrong: sharing the blocking pool meant N blocked script bodies stalled
// every Expression trigger on the node indefinitely. Evaluations are non-blocking by
// construction, so they now run as plain async work on the shared .NET thread pool
// behind TriggerEvalGate.
//
// The expression is therefore TRUE only when evaluated OFF a script-execution thread —
// still off the dispatcher (the P1 property, covered by the coalescing/PipeTo tests),
// and now provably off the blocking pool too.
var expr = CompileRawTriggerExpression(
"System.Threading.Thread.CurrentThread.Name != null && " +
"System.Threading.Thread.CurrentThread.Name.StartsWith(\"script-execution-\")");
"System.Threading.Thread.CurrentThread.Name == null || " +
"!System.Threading.Thread.CurrentThread.Name.StartsWith(\"script-execution-\")");
var (actor, instance) = CreateTriggeredActor(
"ExprThread",
"Expression",
@@ -449,7 +460,7 @@ public class ScriptActorTests : TestKit, IDisposable
expr);
actor.Tell(Change("Any", "1"));
instance.ExpectMsg<SetStaticAttributeCommand>(TimeSpan.FromSeconds(10)); // fired ⇒ evaluated off-dispatcher
instance.ExpectMsg<SetStaticAttributeCommand>(TimeSpan.FromSeconds(10)); // fired ⇒ evaluated off the blocking pool
}
[Fact]