π What happened
PR #716 (dependabot, admin-ui/package-lock.json only β cannot touch .NET) went red on build-test, run 34435202778 attempt 1:
Failed GenWave.Orchestration.Tests.Specs.FeatureDjsHandOffAudibly+ScenarioBlurbCachePosture.RendersRideThePerUnitBudget [271 ms]
Assert.DoesNotContain() Failure: Filter matched in collection β¦ MediaItem { MediaId = tts:signon-2, β¦ SegmentKind = SignOn }
at β¦/tests/GenWave.Orchestration.Tests/Specs/Story243_DjsHandOffAudibly.cs:line 508
The sign-off was dropped, the sign-on aired. It ran at 00:00:01.55 of the Orchestration run β the runner's cold first second.
π Why β the race is still wall-clock
The fact arranges a 10 ms render budget against a 200 ms fake render and expects both handoff pieces dropped. The orchestrator's race is Task.WhenAny(renderTask, Task.Delay(renderBudget, timeProvider, ct)) (src/GenWave.Orchestration/Orchestrator.cs:1624), which gh-#554 made ride the injected clock so a spec can decide it by fake due-order. But:
- Orchestration.Tests' own
FakeTimeProvider (tests/GenWave.Orchestration.Tests/Fakes/FakeTimeProvider.cs) overrides only GetUtcNow. No CreateTimer, so the budget delay falls through to the base TimeProvider's real System.Threading.Timer.
FakeTtsSegmentSource.RenderDelay is a real await Task.Delay(200 ms).
So it is a 10 ms timer versus a 200 ms delay on the wall clock, and both pieces are kicked before the first is awaited (Kick at :1173, the await loop at :1617). Any ~190 ms stall between the kick and the second WhenAny (cold JIT of the first drop's logging/booth-log path, a GC pause, a busy runner) lets the sign-on render finish first, and it airs. The #554 fix went into Host.Tests (tests/GenWave.Host.Tests/Specs/Story139_SettingsSurfaceCompletion.cs: Microsoft's FakeTimeProvider + a DelayableTtsSegmentSource(time) whose delay rides the same clock); this Orchestration fact never got the same treatment.
π§ Fix
Port the Story139 arrangement: Microsoft.Extensions.TimeProvider.Testing into Orchestration.Tests (dotnet add package), and a render double whose delay rides that clock, so the race is decided by due-order and the budget-drop fact cannot be load-sensitive. The hand-rolled FakeTimeProvider is shared by every Orchestration spec β either give it timers or swap it project-wide.
πͺ Same family
tests/GenWave.Tts.Tests/Specs/Story390_AdScriptWriter.cs:454 ASlowFirstDraftDoesNotStarveTheReask β a real 3 s budget against a mock HTTP server's 1.8 s delay. Reds about 1-in-2 on a loaded dev box; the sponsors build loop carried a standing "rerun once" exemption for it through every gate, which means a real regression in that fact would be waved through as the known flake. HttpClient timeouts are wall-clock by nature, so either drive the attempt budget off a TimeProvider (CancellationTokenSource(delay, timeProvider)) and fake it, or widen the ratio to one load cannot erase.
Rerun of #716's job: attempt 2 (result on the PR).
π What happened
PR #716 (dependabot,
admin-ui/package-lock.jsononly β cannot touch .NET) went red on build-test, run 34435202778 attempt 1:The sign-off was dropped, the sign-on aired. It ran at
00:00:01.55of the Orchestration run β the runner's cold first second.π Why β the race is still wall-clock
The fact arranges a 10 ms render budget against a 200 ms fake render and expects both handoff pieces dropped. The orchestrator's race is
Task.WhenAny(renderTask, Task.Delay(renderBudget, timeProvider, ct))(src/GenWave.Orchestration/Orchestrator.cs:1624), which gh-#554 made ride the injected clock so a spec can decide it by fake due-order. But:FakeTimeProvider(tests/GenWave.Orchestration.Tests/Fakes/FakeTimeProvider.cs) overrides onlyGetUtcNow. NoCreateTimer, so the budget delay falls through to the baseTimeProvider's realSystem.Threading.Timer.FakeTtsSegmentSource.RenderDelayis a realawait Task.Delay(200 ms).So it is a 10 ms timer versus a 200 ms delay on the wall clock, and both pieces are kicked before the first is awaited (
Kickat:1173, the await loop at:1617). Any ~190 ms stall between the kick and the secondWhenAny(cold JIT of the first drop's logging/booth-log path, a GC pause, a busy runner) lets the sign-on render finish first, and it airs. The #554 fix went into Host.Tests (tests/GenWave.Host.Tests/Specs/Story139_SettingsSurfaceCompletion.cs: Microsoft'sFakeTimeProvider+ aDelayableTtsSegmentSource(time)whose delay rides the same clock); this Orchestration fact never got the same treatment.π§ Fix
Port the Story139 arrangement:
Microsoft.Extensions.TimeProvider.Testinginto Orchestration.Tests (dotnet add package), and a render double whose delay rides that clock, so the race is decided by due-order and the budget-drop fact cannot be load-sensitive. The hand-rolledFakeTimeProvideris shared by every Orchestration spec β either give it timers or swap it project-wide.πͺ Same family
tests/GenWave.Tts.Tests/Specs/Story390_AdScriptWriter.cs:454ASlowFirstDraftDoesNotStarveTheReaskβ a real 3 s budget against a mock HTTP server's 1.8 s delay. Reds about 1-in-2 on a loaded dev box; the sponsors build loop carried a standing "rerun once" exemption for it through every gate, which means a real regression in that fact would be waved through as the known flake.HttpClienttimeouts are wall-clock by nature, so either drive the attempt budget off aTimeProvider(CancellationTokenSource(delay, timeProvider)) and fake it, or widen the ratio to one load cannot erase.Rerun of #716's job: attempt 2 (result on the PR).