docs: tighten the comments in Main.cs

Comments only -- no code changed. Drops the narrative and development
framing that accumulated through the idle-sleep work ("now genuinely
blocks", "checked on every sample rather than only on bad ones", the
worked example of why a count is not a rate) and keeps the load-bearing
rationale in one or two lines each: why the tick-count sentinel is a
count, why server work cannot trip the backoff, why the GC read is
short-circuited, why prompts run before Serilog.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-08-13 19:32:03 -07:00
parent 6a02d4dece
commit d4c2894555
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A

View file

@ -39,21 +39,19 @@ public static class Core
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Core));
// Written from other threads (Kill, RequestSnapshot) and read by the event loop. Volatile
// because the loop now genuinely blocks between reads rather than spinning past them.
// Written off-loop (Kill, RequestSnapshot); volatile because the loop blocks between reads.
private static volatile bool _performProcessKill;
private static bool _restartOnKill;
private static volatile bool _performSnapshot;
private static string _snapshotPath;
// A backstop, not a latency control: the wheel's tick rate bounds the sleep, so this only
// limits the damage if a wake signal is ever missed. Measured across 1/2/4/8ms, 2 is optimal.
// A backstop, not a latency control: the wheel's tick rate already bounds the sleep.
// Measured across 1/2/4/8ms; 2 is optimal.
private static int _eventLoopIdleWaitMs = 2;
/// <summary>
/// Longest the loop will block while idle, in milliseconds. 0 disables idle sleeping,
/// leaving the loop to spin; the adaptive backoff does the same thing temporarily when the
/// host keeps returning waits late.
/// Longest the loop will block while idle, in milliseconds. 0 spins instead; the backoff
/// does the same temporarily when the host keeps returning waits late.
/// </summary>
public static int EventLoopIdleWaitMs => _eventLoopIdleWaitMs;
@ -74,9 +72,8 @@ public static class Core
private const long HealthSampleIntervalMs = 1000;
// Backoff escalates by doubling: a fixed suspension oscillates forever on a persistently bad
// host, while doubling converges on "stop sleeping" within minutes yet still recovers from a
// transient problem.
// Doubling: a fixed suspension oscillates forever on a persistently bad host, while doubling
// converges on "stop sleeping" yet still recovers from a transient.
private const long BackoffBaseMs = 5000;
private const long BackoffMaxMs = 120_000;
private const int BackoffMaxShift = 5;
@ -84,19 +81,14 @@ public static class Core
// Clean streak that clears the escalation.
private const long BackoffResetAfterCleanMs = 60_000;
// Backoffs below this are still recoverable and not something an operator can act on, so they
// are logged at Debug. Past it the host has stayed bad through several doublings and is worth
// a Warning; the ceiling above escalates to Error.
// Below this a backoff is still recoverable and not actionable, so it only logs at Debug.
private const int WarnAfterConsecutiveBackoffs = 3;
// A sleep is bounded by the time to the next wheel turn, so a correctly honoured sleep can
// never miss a deadline; the only way sleeping harms the wheel is the wait returning late
// (the host descheduled the process). That overshoot is measured per sleep, which is why
// server work -- saves, heavy commands, deep timer callbacks -- cannot trip this backoff.
// Loop-thread only, so plain increments are safe.
// A sleep is bounded by the next wheel turn, so only a wait returning late can cost a deadline.
// Measured per sleep, which is why server work (saves, heavy commands) cannot trip the backoff.
private static int _lateWakes;
// Denominator for the late-wake rate: sleeps actually performed in the current sample window.
// Denominator for the late-wake rate.
private static int _sleepAttempts;
private static long _nextHealthSample;
@ -128,11 +120,8 @@ public static class Core
_lateWakes = 0;
_sleepAttempts = 0;
// A long clean streak resets the escalation, re-arming the ceiling Error so a host that
// recovers and later degrades again gets re-reported. Checked on every sample rather than
// only on bad ones, so a host that recovers for good still says so instead of waiting to
// degrade again. Gated on the count rather than a "_lastBackoffAt > 0" sentinel because
// tick counts are not guaranteed positive.
// A clean streak resets the escalation and re-arms the ceiling Error. Gated on the count
// rather than a "_lastBackoffAt > 0" sentinel because tick counts are not guaranteed positive.
if (_consecutiveBackoffs > 0 && _tickCount - _lastBackoffAt > BackoffResetAfterCleanMs)
{
if (_consecutiveBackoffs >= WarnAfterConsecutiveBackoffs)
@ -153,20 +142,17 @@ public static class Core
return;
}
// Lateness is a rate, not a count. An idle loop performs hundreds of ~2ms sleeps a second,
// so a handful of outliers -- a co-tenant burst, a page fault, another process changing the
// system timer resolution -- is normal on a perfectly healthy host. A host that genuinely
// cannot schedule the process returns *most* of its waits late, which is two orders of
// magnitude away. The absolute threshold stays on as a floor for windows with few sleeps,
// where a proportion means nothing.
// Lateness is a rate: an idle loop sleeps hundreds of times a second, so a few outliers are
// normal, while a host that cannot schedule the process returns most of its waits late. The
// threshold above is the floor for windows with too few sleeps for a proportion to mean anything.
if (late * 100 < sleeps * _lateWakePercent)
{
_consecutiveBadSamples = 0;
return;
}
// Require the condition to persist: any host can drop one sample to unrelated load, and a
// host that is genuinely oversubscribed stays that way, so it trips on the second sample.
// Require persistence: any host can drop one sample to unrelated load, but an oversubscribed
// one stays bad.
if (++_consecutiveBadSamples < 2)
{
return;
@ -185,7 +171,7 @@ public static class Core
if (_currentBackoffMs >= BackoffMaxMs)
{
// Escalation has run out of room; say so once in terms the operator can act on.
// Escalation has run out of room; say so once.
if (!_loggedBackoffCeiling)
{
_loggedBackoffCeiling = true;
@ -200,9 +186,8 @@ public static class Core
return;
}
// Each backoff doubles the suspension, so every one of these is a distinct escalation step
// and needs no further rate limiting. The first couple are recoverable and not actionable,
// so they stay at Debug; only a host that survives several doublings earns a Warning.
// Each backoff doubles the suspension, so every line is a distinct escalation step and
// needs no further rate limiting.
if (_consecutiveBackoffs < WarnAfterConsecutiveBackoffs)
{
logger.Debug(
@ -414,8 +399,8 @@ public static class Core
_restartOnKill = restart;
_performProcessKill = true;
// Callers are usually off-loop (console input, signal handlers). Without this the loop
// would not notice the request until it woke for some other reason.
// Callers are usually off-loop (console input, signal handlers); wake so the request
// is noticed now rather than whenever the loop next surfaces.
NetState.Wake();
}
@ -618,9 +603,8 @@ public static class Core
_eventLoopIdleWaitMs = Math.Max(0, idleWaitMs);
// Floor for the late-wake backoff: idle waits per second the host may return a full tick
// (8ms) late before the rate test below even applies. Raise to tolerate a jittery host; set
// very high to disable the backoff.
// Floor for the backoff: idle waits per second the host may return a full tick late before
// the rate test below applies at all. Set very high to disable the backoff.
var lateWakeThreshold = ServerConfiguration.GetSetting("server.lateWakeThreshold", 1);
if (lateWakeThreshold < 0)
{
@ -632,10 +616,8 @@ public static class Core
_lateWakeThreshold = Math.Max(0, lateWakeThreshold);
// Share of a second's idle waits that must come back late before the backoff trips. A bare
// count cannot separate a few tail outliers from a host that never schedules us -- an idle
// loop sleeps hundreds of times a second -- but the proportion can. 0 leaves the absolute
// threshold in sole charge.
// Share of a second's idle waits that must return late before the backoff trips. 0 leaves
// the threshold above in sole charge.
var lateWakePercent = ServerConfiguration.GetSetting("server.lateWakePercent", 10);
if (lateWakePercent is < 0 or > 100)
{
@ -664,10 +646,8 @@ public static class Core
AssemblyHandler.LoadAssemblies(assemblyFiles);
// First-boot interactive setup. Runs after assemblies are loaded (so content can
// register prompts) but before any Serilog output, so console prompts are not
// interleaved with the async console sink. Handlers self-gate on first-boot state
// (e.g. "is my setting already present?").
// First-boot interactive setup. After assemblies load so content can register prompts,
// before any Serilog output so prompts are not interleaved with the async console sink.
AssemblyHandler.Invoke("ConfigurePrompts");
logger.Information("Running on {Framework}", RuntimeInformation.FrameworkDescription);
@ -677,9 +657,8 @@ public static class Core
_now = DateTime.UtcNow;
_firstTick = _tickCount = GetTimestamp();
// Seed schedule state from the first real tick: tick counts are not guaranteed to start
// anywhere near zero (hypervisor pass-through counters), so zero-initialized deadlines
// would compare wrong. See dev-docs/tick-counts.md.
// Seed from a real tick: tick counts need not start near zero, so a zero-initialized
// deadline compares wrong. See dev-docs/tick-counts.md.
_nextHealthSample = _tickCount + HealthSampleIntervalMs;
_idleSleepSuspendedUntil = _tickCount;
@ -700,9 +679,8 @@ public static class Core
PingServer.Start();
EventSink.InvokeServerStarted();
// Without a high-resolution wait a 2ms request quantises to 15.625ms and the loop would
// quietly run a tick behind; spinning is the lesser evil and must not be silent. Only
// fires when both the ring's high-res timer and its timeBeginPeriod fallback failed.
// Without a high-resolution wait a 2ms request quantises to 15.625ms and the loop runs a
// tick behind. Only fires when the high-res timer and the timeBeginPeriod fallback both failed.
if (_eventLoopIdleWaitMs > 0 && NetState.Ring?.SupportsHighResolutionWait == false)
{
logger.Error(
@ -719,8 +697,7 @@ public static class Core
/// <summary>
/// True when every queue the loop drains is empty, so sleeping cannot strand pending work.
/// The drains are bounded (ProcessDeltaQueue stops at the count seen on entry, ExecuteTasks
/// at its per-frame cap), so leftovers are normal and must keep the loop awake.
/// The drains are bounded, so leftovers are normal and must keep the loop awake.
/// </summary>
private static bool IsIdle() =>
!Mobile.HasQueuedDeltas && !Item.HasQueuedDeltas && LoopContext.IsEmpty && NetState.IsIdle;
@ -779,18 +756,17 @@ public static class Core
if (_eventLoopIdleWaitMs > 0 && _tickCount - _idleSleepSuspendedUntil >= 0 && IsIdle())
{
// Re-read the clock: the loop body consumed real time, and a stale timestamp
// would overstate the time to the next tick and sleep straight past it.
// Re-read the clock: a stale timestamp overstates the time to the next tick
// and sleeps straight past it.
var start = GetTimestamp();
var due = Timer.MillisecondsUntilNextTick(start);
if (due > 0)
{
var requested = (int)Math.Min(due, _eventLoopIdleWaitMs);
// The GC deliberately collects during idle sleeps -- that is the natural
// pause point it looks for -- so its pauses land here by design and must
// not be charged to the host. Gen1 and above are the only ones whose pause
// approaches a tick; CollectionCount(1) counts those and gen2 with it.
// The GC prefers to collect during idle sleeps, so its pauses land here by
// design and are not the host's fault. Gen1 and above (what
// CollectionCount(1) counts) are the only pauses long enough to reach a tick.
var collections = GC.CollectionCount(1);
NetState.WaitForCompletion(requested);
@ -799,10 +775,8 @@ public static class Core
EventLoopProfiler.SleepEnd(requested, elapsed);
_sleepAttempts++;
// A sleep is bounded by the next wheel turn, so only a wait the host
// returned late can cost the wheel a deadline. The second collection read
// is short-circuited behind the overshoot test, so the common path pays for
// one counter read, not two.
// The second collection read sits behind the overshoot test, so the common
// path reads the counter once, not twice.
if (elapsed - requested >= Timer.TickRate && GC.CollectionCount(1) == collections)
{
_lateWakes++;
@ -825,8 +799,7 @@ public static class Core
_snapshotPath = snapshotPath;
_performSnapshot = true;
// Save requests arrive off-loop. Wake so the snapshot starts now rather than after the
// loop happens to surface for another reason.
// Save requests arrive off-loop; wake so the snapshot starts now.
NetState.Wake();
}