Recover the video mirror from a WAIT_ABANDONED keyed mutex

SharedTextureSource::update() acquired the shared-texture keyed mutex with
`AcquireSync(...) == S_OK`. But WAIT_ABANDONED -- a prior owner (e.g. a host that
crashed mid-acquire, then reconnected) died holding it -- actually GRANTS us
ownership. Treating it as failure skipped the copy AND never released, so the
next AcquireSync blocked forever and the mirror froze permanently after a crash
+ reconnect (directly relevant to the new reconnect path).

Factor the decision into keyed_mutex_acquired(HRESULT) (capture/keyed_mutex.hpp):
S_OK or WAIT_ABANDONED -> copy + release; timeout/hard errors -> skip the frame.
update() now uses it.

Test-first: keyed_mutex_test asserts WAIT_ABANDONED is treated as acquired while
the genuine "didn't get it" cases (timeout, E_FAIL, device-removed) are not. The
full cross-process abandonment is keyed-mutex OS semantics, not re-tested with a
child process here -- the predicate is the regression surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 01:12:29 +02:00
parent 8182389091
commit bdb700ec56
5 changed files with 75 additions and 6 deletions

View File

@@ -0,0 +1,21 @@
// The acquire decision for the shared video texture's keyed mutex, factored out so it can be tested
// without a D3D device (capture/shared_texture.cpp uses it).
#pragma once
#include <windows.h> // HRESULT, S_OK, WAIT_ABANDONED, WAIT_TIMEOUT
namespace coop
{
// True when an IDXGIKeyedMutex::AcquireSync result means we now HOLD the mutex and must copy + then
// release it. S_OK is the normal case. WAIT_ABANDONED is success-with-recovery: a previous owner
// (e.g. a host that crashed while holding it, then reconnected) terminated without releasing, so the
// OS hands ownership to us -- treating it as a failure would skip the copy AND leak ownership, so the
// next AcquireSync would block forever and the mirror would freeze. Timeout / hard errors mean "we
// didn't get it, skip this frame."
inline bool keyed_mutex_acquired(HRESULT acquire_result)
{
return acquire_result == S_OK || acquire_result == static_cast<HRESULT>(WAIT_ABANDONED);
}
} // namespace coop