TRAX: a share that is gone must never freeze the startup window
Startup took 44s and 42 of them were one line. Measured from startup.log: OnStartup 08:42:59 -> MainWindow loaded 08:43:43, with the whole gap inside LoadLayout, ending at TRAX: failed to load '\\192.168.50.86\llamaVault\Assets\Music\...': The network path was not found. (0x80070035) LoadTrax called MusicPlayer.Load synchronously on the UI thread, and new MediaFoundationReader(path) does not fail fast on a dead SMB session — it burns the full ARP/TCP/SMB timeout and only then throws. The NAS had moved to .87 after the power outage, so an unreachable path became a 40-second frozen window on every launch. The rule, from the creator: a probe may time out; a bind should return immediately. Only one side of that is achievable here, and the reason matters — an unreachable UNC path has no fast-fail at all, so probing it on the calling thread is the worst possible probe. (A device open is the opposite: it normally fails fast on a sharing violation, which is why this bug is network-specific.) So: IsTrackReachableAsync runs the probe on a thread-pool thread under a Task.WhenAny ceiling (TraxProbeBudget, 2s) and treats offline/missing/slow as a normal state rather than an error. Only a probe that passed gets the real bind, and that bind deliberately stays on the UI thread alongside every other MusicPlayer mutation — moving the whole load off-thread would have introduced a race against PlayTrax/PauseTrax that does not exist today. The orphan probe thread stays parked until SMB gives up; it is not the thread anyone waits on. One test, per the Good Dog rule: TraxProbeTests. AHangingProbeIsAbandonedAtTheBudget_AndReportsOffline stands in a probe that never returns and asserts the caller is released and told "offline". Drop the ceiling and it fails on elapsed time — which is the frozen window returning. Suite 381/381, 0 warnings.
This commit is contained in:
@@ -871,6 +871,22 @@ devices, no timers).
|
||||
track persists via **schema v9** single-row `Music` (`TrackPath`/`IsEnabled`) in `LayoutStore`. Known
|
||||
wrinkle (out of scope, future feature): YouTube mutes VODs carrying copyrighted music — "music on
|
||||
live, off VOD" is tabled.
|
||||
- **⛔ The load is behind a bounded probe, because a share that is gone must never
|
||||
freeze startup (2026-10-04).** `LoadLayout` → `LoadTrax` used to call `MusicPlayer.Load`
|
||||
synchronously on the UI thread, and `new MediaFoundationReader(path)` does **not** fail
|
||||
fast on a dead SMB session — it burns the whole ARP/TCP/SMB timeout (~40s, measured)
|
||||
and *then* throws `0x80070035`. Measured: `OnStartup` 08:42:59 → `MainWindow loaded`
|
||||
08:43:43, with 42 of those 44s inside `LoadLayout` on that one track. The rule now:
|
||||
**a probe may time out; only a bind is allowed to be on the creator's path.**
|
||||
`IsTrackReachableAsync(path, budget, File.Exists)` runs the probe on a thread-pool
|
||||
thread under a `Task.WhenAny` ceiling (`TraxProbeBudget` = **2s**) and reports
|
||||
offline/missing/slow as a normal state, not an error. Only a probe that passed gets
|
||||
the real bind, and **that bind stays on the UI thread** with every other
|
||||
`MusicPlayer` mutation, so no new cross-thread race is introduced. The orphan probe
|
||||
thread stays parked until SMB gives up — deliberate: it is not the thread anyone
|
||||
waits on. Same principle covers the webcam: a device held by another app fails fast
|
||||
(sharing violation) but a virtual-camera driver can stall enumeration, so device
|
||||
probes belong off the startup path too, not behind a long timeout.
|
||||
- **Mic status dot (`Models/MicStatus.cs`)** on the preview-bottom MIC button: green = `MicConnected`,
|
||||
yellow = `MicFailed` (in use/unplugged), red = no mic device at startup (the mixer is never started,
|
||||
so loopback and the game bar can't run either — no capture devices at all). **The picked mic persists:
|
||||
|
||||
Reference in New Issue
Block a user