Table of Contents

Class DagScheduler

Namespace
Typhon.Engine
Assembly
Typhon.Engine.dll

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
HighResolutionTimerServiceBase
DagScheduler
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

systems SystemDefinition[]

System definitions from Build().

topologicalOrder int[]

Topological order from Build().

tracks IReadOnlyList<Track>

The track hierarchy in execution order — each track carries its DAGs and their resolved system indices.

deferredTrackStartIndex int

Tracks 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.

options RuntimeOptions

Runtime configuration.

parent IResource

Parent resource node (typically Runtime).

eventQueues EventQueueBase[]

Event queues to reset at each tick start. Can be empty.

logger ILogger

Optional 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

Action<int, string, Exception>

Properties

AllSystemCount

Total number of systems in the DAG, including engine-internal systems (e.g. FenceExec).

public int AllSystemCount { get; }

Property Value

int

CurrentOverloadLevel

Current overload response level.

public OverloadLevel CurrentOverloadLevel { get; }

Property Value

OverloadLevel

CurrentTickNumber

Number of ticks executed so far.

public long CurrentTickNumber { get; }

Property Value

long

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

IReadOnlyList<EventQueueBase>

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

int

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

SystemDefinition[]

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

TickTelemetryRing

ThreadName

Thread name set by derived classes for diagnostics.

protected override string ThreadName { get; }

Property Value

string

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

IReadOnlyList<Track>

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

SystemDefinition[]

WorkerCount

Number of worker threads.

public int WorkerCount { get; }

Property Value

int

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

trackIndex int

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

disposing bool

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

string

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

scheduledTick long

The Stopwatch timestamp that was targeted (as returned by Typhon.Engine.Internals.HighResolutionTimerServiceBase.GetNextTick()).

actualTick long

The 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

scheduledTimestamp long
startTimestamp long
endTimestamp long
phaseFlags byte

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

context TContext

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()