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:
21
host/src/capture/keyed_mutex.hpp
Normal file
21
host/src/capture/keyed_mutex.hpp
Normal 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
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "capture/shared_texture.hpp"
|
||||
|
||||
#include "capture/dxgi_format.hpp"
|
||||
#include "capture/keyed_mutex.hpp"
|
||||
#include "coop/protocol.hpp"
|
||||
#include "coop/shared_memory.hpp"
|
||||
|
||||
@@ -194,8 +195,10 @@ bool SharedTextureSource::update(const VideoShareView& share, unsigned long pid)
|
||||
return srv_ != nullptr; // no new frame; keep showing the last copy
|
||||
}
|
||||
|
||||
// Bounded wait so a stalled producer can't hang the host's render thread.
|
||||
if (mutex_->AcquireSync(kVideoMutexKey, 8) == S_OK)
|
||||
// Bounded wait so a stalled producer can't hang the host's render thread. WAIT_ABANDONED (a prior
|
||||
// owner died holding the mutex -- e.g. a host that crashed and reconnected) counts as acquired:
|
||||
// recover by copying + releasing rather than skipping, which would hold it forever and freeze.
|
||||
if (keyed_mutex_acquired(mutex_->AcquireSync(kVideoMutexKey, 8)))
|
||||
{
|
||||
ctx_->CopyResource(private_.Get(), shared_.Get());
|
||||
mutex_->ReleaseSync(kVideoMutexKey);
|
||||
|
||||
Reference in New Issue
Block a user