forked from unom/punktfunk
Three Windows NVENC teardown/accounting defects (audit WP5.2, marked BLOCKING — the obvious fixes were reviewed and rejected before this): Event-handle leak: init_session pushed each completion event to self.events only AFTER nvEncRegisterAsyncEvent, so a failed registration leaked the Win32 handle. Push first — the init-failure path already runs teardown, which closes everything in the list (unregistering the one never-registered event is a harmless error return). Wedged drain: teardown drains the retrieve thread's backlog, and each queued job could burn its full 5 s completion wait — cap x 5 s on the encode thread, paid again on every rebuild of the stall-recovery ladder. The rejected fix (a shutdown flag) abandoned the backlog on EVERY teardown, turning the routine drain into destroy-while-encoding. Instead the wedge evidence stays where it lives: after one full-budget timeout in the retrieve loop, later jobs wait a 250 ms slice; a success resets the latch. Routine teardown is byte-identical to before — nothing is ever abandoned — and a first timeout is already encoder-fatal, so short slices behind an established wedge change only how long teardown blocks. Session-unit accounting: LIVE_SESSION_UNITS was refunded even when destroy_encoder FAILED, drifting the budget low and over-admitting parallel displays. The rejected fix (never refund on failure) let one transient wedge episode poison admission until a host restart. Now the refund follows PROOF: destroy succeeded, or its status says the driver holds no session (device gone/TDR). Ambiguous failures park the handle — units still charged, fail-closed, pinning the D3D11 device the driver session references — and init_session retries the destroy while zero sessions are live, under a gate that serializes every session open against the reap so a recycled handle address can never alias a live session. The charge lasts exactly as long as the driver keeps refusing the destroy, which is the definition of still-leaked. Admission stays a lock-free atomic load. Both-platform gates green (docker linux/amd64 4 legs; .173 all 7 legs). The retry-destroy-on-a-failed-handle assumption is documented at the reap: the SDK defines no retry semantics either way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>