fix(app): #343 — a wounded render loop defers the native release instead of throwing over the real failure
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run

Root cause pinned by IL-decompiling Silk.NET.Windowing.Common:
ViewImplementationBase._inRenderLoop is set at DoRender/DoUpdate entry
and cleared ONLY on normal return, so a throwing frame callback leaves
it armed forever and any later Dispose -> Reset throws "You cannot call
Reset inside of the render loop", exit 82, replacing the original
wounding exception in the report.

The fix mirrors Silk's own bracket exactly: GameWindow._renderLoopArmed
set at OnUpdate/OnRender entry, cleared only on their normal return —
deliberately NOT in a finally, so it tracks the wound the same way
Silk's private field does. ReleaseNativeWindow checks it before
disposing: armed -> best-effort Close() (swallowed so it can never
become the reported failure), no Dispose, and a new terminal status
CompleteWithDeferredNativeRelease with Error kept null — the original
exception stays the primary report. Healthy paths (OnClosing's
in-loop completion, Run()'s tail release) are byte-unchanged, and the
new PublishNativeWindow parameters default to null so every existing
caller and test behaves identically.

Sabotage: disabling the armed-check flipped the deferral test to
Expected CompleteWithDeferredNativeRelease / Actual Complete —
the guard is what the test exercises. Clean-room suite 11,262 / 6 / 1,
the 1 being #340's documented load flake (passed standalone; second
recorded firing noted in its entry).

Queue: #344 done, #343 done; next #345's instrumented mechanism
session, then #341's boundary hunt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-07 10:28:29 +02:00
parent 52bdf4df71
commit 6c6664a685
4 changed files with 194 additions and 10 deletions

View file

@ -257,7 +257,19 @@ mid-flight-teleport-only. Next-session queue: #344, #343, then S4b/S6.
## #343 — Shutdown after a wounded render loop: Silk Reset called inside the render loop, exit 82
**Status:** OPEN. LOW-MEDIUM — only reachable after a render-frame exception
**Status:** FIXED 2026-08-08. Root cause pinned by IL-decompiling Silk:
`ViewImplementationBase._inRenderLoop` is cleared only on a frame callback's
NORMAL return, so a throwing callback leaves it armed forever and the
disposal's `Reset()` throws. The fix mirrors that bracket exactly
(`GameWindow._renderLoopArmed`, deliberately not cleared in a finally),
and `ReleaseNativeWindow` defers when armed: best-effort `Close()`, no
`Dispose()`, new terminal status `CompleteWithDeferredNativeRelease` with
`Error` kept null so the ORIGINAL wounding exception remains the primary
report. Sabotage-verified; healthy paths byte-unchanged. Clean-room suite
11,262 passed / 6 skipped / 1 failed = #340's documented flake, which
passed standalone (its second recorded firing).
**Original status:** OPEN. LOW-MEDIUM — only reachable after a render-frame exception
(#339's crash was the trigger), but it turns a diagnosable failure into an
`AbandonedIncomplete` shutdown. `GameWindowLifetime.ReleaseNativeWindow:294`
calls `ViewImplementationBase.Dispose``Reset` while the render loop is