Class DagScheduler
Production DAG scheduler for the Typhon Runtime. Executes a static system DAG on a pool of worker threads, with any-worker dispatch and inline continuation.
public sealed class DagScheduler : HighResolutionTimerServiceBase, IResource, IDisposable, IMetricSource
- Inheritance
-
HighResolutionTimerServiceBaseDagScheduler
- Implements
- Inherited Members
- Extension Methods
Remarks
Derives from Typhon.Engine.Internals.HighResolutionTimerServiceBase to leverage its three-phase wait (Sleep → Yield → Spin), self-calibrated sleep threshold, timing error tracking, and ResourceNode lifecycle.
The timer thread acts as a metronome: it resets per-tick state, bumps the generation counter (waking workers), and waits for tick completion. All dispatch decisions happen on workers (POC decision D2: any-worker dispatch, no scheduler thread).
Between ticks, workers use a three-phase wait referencing _nextTickTimestamp (set by the timer thread). This saves CPU during the idle gap while
maintaining sub-microsecond wake latency in the final spin phase.
Constructors
DagScheduler(SystemDefinition[], int[], IReadOnlyList<Track>, int, RuntimeOptions, IResource, EventQueueBase[], ILogger)
Creates a new DAG scheduler.
public DagScheduler(SystemDefinition[] systems, int[] topologicalOrder, IReadOnlyList<Track> tracks, int deferredTrackStartIndex, RuntimeOptions options, IResource parent, EventQueueBase[] eventQueues = null, ILogger logger = null)
Parameters
systemsSystemDefinition[]System definitions from Build().
topologicalOrderint[]Topological order from Build().
tracksIReadOnlyList<Track>The track hierarchy in execution order — each track carries its DAGs and their resolved system indices.
deferredTrackStartIndexintTracks from this index onward are dispatched on demand by the runtime (e.g. Engine-Post after serial fence prep), not by the in-tick track loop.
optionsRuntimeOptionsRuntime configuration.
parentIResourceParent resource node (typically Runtime).
eventQueuesEventQueueBase[]Event queues to reset at each tick start. Can be empty.
loggerILoggerOptional logger for diagnostics.
Fields
UnhandledExceptionCallback
Public hook fired when the WorkerLoop's outer safety net catches an unhandled exception that escaped every inner try/catch (engine bug, runaway exception from inside a catch handler, etc.). User code can subscribe to log to file, send to telemetry, request graceful shutdown, or escalate to a debugger break. Invoked on the worker thread that caught the exception. Defaults to null (no-op).
Note: The vast majority of user-code exceptions are already caught by the per-system handlers in
Typhon.Engine.DagScheduler.ProcessParallelQuery(System.Int32,System.Int32,System.Boolean), Typhon.Engine.DagScheduler.ProcessCallbackOrQuery(System.Int32,System.Int32,System.Boolean), Typhon.Engine.DagScheduler.ProcessPipeline(System.Int32,System.Int32,System.Boolean), and
Typhon.Engine.DagScheduler.ExecuteInline(System.Int32,System.Int32,System.Boolean). Those paths log via LogSystemException and capture via
CaptureSystemException without invoking this callback. This callback fires ONLY when the outer WorkerLoop safety net catches something — meaning
an inner handler didn't, which is itself a bug worth surfacing prominently.
public Action<int, string, Exception> UnhandledExceptionCallback
Field Value
Properties
AllSystemCount
Total number of systems in the DAG, including engine-internal systems (e.g. FenceExec).
public int AllSystemCount { get; }
Property Value
CurrentOverloadLevel
Current overload response level.
public OverloadLevel CurrentOverloadLevel { get; }
Property Value
CurrentTickNumber
Number of ticks executed so far.
public long CurrentTickNumber { get; }
Property Value
EventQueues
All registered event queues. Read-only view exposed for static-data introspection (the profiler builds the v7 EventQueueRecord catalog from this) — not for hot-path use; system code should go through the pre-allocated ConsumedQueues array instead.
public IReadOnlyList<EventQueueBase> EventQueues { get; }
Property Value
SystemCount
Number of user-registered systems (systems whose track does not carry the EngineTag — i.e. excludes the Fence DAG). This is the count callers usually want — "the systems I registered". For the total including engine-internal systems use AllSystemCount.
public int SystemCount { get; }
Property Value
Systems
All registered systems across every track and DAG, indexed by system index. Used by the scheduler for dispatch, lookup, and topological reasoning — the index identity is load-bearing for chunk dispatch state and dependency wiring, so this array stays as the canonical source for everything index-keyed (including diagnostics/profiler that walk every entry by raw index).
public SystemDefinition[] Systems { get; }
Property Value
Remarks
When you only care about systems the user registered (counting them in a test, iterating them in a DAG-viewer that hides engine plumbing), prefer UserSystems — it filters out systems whose track carries the EngineTag (e.g. the Fence DAG) so the count is stable regardless of which engine-internal DAGs the runtime registers.
Telemetry
Telemetry ring buffer for diagnostic inspection.
public TickTelemetryRing Telemetry { get; }
Property Value
ThreadName
Thread name set by derived classes for diagnostics.
protected override string ThreadName { get; }
Property Value
Tracks
The runtime partitioning hierarchy — tracks in execution order, each carrying its DAGs. Static for the scheduler's lifetime.
public IReadOnlyList<Track> Tracks { get; }
Property Value
UserSystems
User-registered systems only (filtered Systems with engine-tagged-track entries removed). Indices in this view do NOT match the canonical system index — use this only for iteration and counting, not for index-keyed lookups.
public SystemDefinition[] UserSystems { get; }
Property Value
WorkerCount
Number of worker threads.
public int WorkerCount { get; }
Property Value
Methods
DispatchDeferredTracks()
Dispatches every runtime-deferred track (Engine-Post and beyond) in execution order. Called by TyphonRuntime.OnTickEndInternal after the serial
WriteTickFence prep, so the Fence DAG sees a populated FenceContext. No-op when every deferred track is empty (e.g. parallel fence disabled).
public void DispatchDeferredTracks()
DispatchTrack(int)
Dispatches a single track by its execution-order index. The in-tick tracks are dispatched automatically each tick; the runtime calls this (via DispatchDeferredTracks()) for the deferred Engine-Post track after serial fence prep. Branches on worker count: single-threaded mode runs the track synchronously on the caller.
public void DispatchTrack(int trackIndex)
Parameters
trackIndexint
Dispose(bool)
Stops the timer thread and disposes managed resources. Follows the standard ResourceNode disposal pattern. Idempotent with Typhon.Engine.Internals.HighResolutionTimerServiceBase.StopTimerThread() — calling this after the timer has already been stopped just runs the managed-resource cleanup.
protected override void Dispose(bool disposing)
Parameters
disposingbool
DumpHangDiagnostic()
Returns a multi-line snapshot of the scheduler's current tick state, suitable for logging when a tick fails to complete. Cheap enough to call ad-hoc — O(systemCount) reads of internal counters.
public string DumpHangDiagnostic()
Returns
ExecuteCallbacks(long, long)
Execute the callback(s) due at this tick. Called by the timer loop immediately after the three-phase wait completes.
protected override void ExecuteCallbacks(long scheduledTick, long actualTick)
Parameters
scheduledTicklongThe Stopwatch timestamp that was targeted (as returned by Typhon.Engine.Internals.HighResolutionTimerServiceBase.GetNextTick()).
actualTicklongThe Stopwatch timestamp when the wait actually completed. The difference
abs(actualTick - scheduledTick)is the timing error for this tick.
GetNextTick()
Returns the Stopwatch timestamp of the next tick to wait for. Called by the timer loop after each callback execution to determine the next wake-up time.
protected override long GetNextTick()
Returns
- long
The GetTimestamp() value at which the next tick should fire. Return MaxValue to idle indefinitely (e.g., no registrations).
Remarks
Returning a timestamp in the past causes the wait loop to exit immediately and call ExecuteCallbacks(long, long) without delay (catch-up behavior). Derived classes should use metronome-style advancement (nextTick += interval) rather than computing from current time, to prevent drift accumulation.
OnWaitComplete(long, long, long, byte)
Emit a SchedulerMetronomeWait span for the inter-tick wait that just completed. Called on the TickDriver thread by Typhon.Engine.Internals.HighResolutionTimerServiceBase.
protected override void OnWaitComplete(long scheduledTimestamp, long startTimestamp, long endTimestamp, byte phaseFlags)
Parameters
Remarks
Why this exists (issue #289 follow-up). Without this hook the timer thread's three-phase wait between ticks emits no profiler events — appearing
as dead time on the trace even when the engine is intentionally throttling itself via Typhon.Engine.Internals.OverloadDetector.TickMultiplier. The span carries
the multiplier and an intentClass byte (CatchUp / Throttled / Headroom) so a trace viewer can answer why the metronome was waiting.
RegisterContext<TContext>(TContext)
Bind an ambient TContext onto every registered system deriving from ChunkedCallbackSystem<TContext>.
Must be called after the schedule is built (systems must already be registered) and before Start().
public void RegisterContext<TContext>(TContext context) where TContext : class
Parameters
contextTContext
Type Parameters
TContext
Shutdown()
Shuts down the scheduler: stops accepting new ticks, waits for the current tick to finish, joins worker threads, then stops the timer thread.
public void Shutdown()
Start()
Starts the worker threads and the tick driver timer thread.
public void Start()