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:
@@ -173,6 +173,12 @@ add_executable(srgb_format_test srgb_format_test.cpp)
|
||||
target_include_directories(srgb_format_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
|
||||
add_test(NAME srgb_format_test COMMAND srgb_format_test)
|
||||
|
||||
# Unit test for the keyed-mutex acquire decision (capture/keyed_mutex.hpp): WAIT_ABANDONED must be
|
||||
# treated as "acquired" so a mirror recovers after a host crash instead of freezing. Header-only.
|
||||
add_executable(keyed_mutex_test keyed_mutex_test.cpp)
|
||||
target_include_directories(keyed_mutex_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
|
||||
add_test(NAME keyed_mutex_test COMMAND keyed_mutex_test)
|
||||
|
||||
# In-process self-test for the Present-hook video path. Reuses the shipping
|
||||
# present_hook.cpp and drives a real D3D11 swapchain in the same process, so it
|
||||
# exercises the IDXGISwapChain::Present inline hook, the shared keyed-mutex
|
||||
@@ -311,6 +317,7 @@ coop_output_subdir(tests
|
||||
dinput_hook_test
|
||||
audio_ring_test
|
||||
ipc_server_test
|
||||
keyed_mutex_test
|
||||
detour_gate_test
|
||||
hook_install_test
|
||||
mkb_ring_test
|
||||
|
||||
42
tests/keyed_mutex_test.cpp
Normal file
42
tests/keyed_mutex_test.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Unit test for the keyed-mutex acquire decision behind SharedTextureSource::update()
|
||||
// (host/src/capture/keyed_mutex.hpp). The bug: AcquireSync returning WAIT_ABANDONED -- a prior owner
|
||||
// (e.g. a host that crashed mid-acquire) died holding the mutex, which GRANTS us ownership -- was
|
||||
// treated as failure, so the host skipped the frame AND never released, freezing the mirror forever.
|
||||
// The predicate must treat WAIT_ABANDONED as "acquired" (copy + release) while still rejecting the
|
||||
// real "didn't get it" cases. Deterministic; no D3D device.
|
||||
#include <cstdio>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <dxgi.h>
|
||||
|
||||
#include "capture/keyed_mutex.hpp"
|
||||
|
||||
using namespace coop;
|
||||
|
||||
namespace
|
||||
{
|
||||
int g_failures = 0;
|
||||
void check(bool ok, const char* what)
|
||||
{
|
||||
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
|
||||
if (!ok)
|
||||
{
|
||||
++g_failures;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
check(keyed_mutex_acquired(S_OK), "S_OK -> acquired (copy + release)");
|
||||
check(keyed_mutex_acquired(static_cast<HRESULT>(WAIT_ABANDONED)),
|
||||
"WAIT_ABANDONED -> acquired (recover: prior owner died holding it; we own it now)");
|
||||
check(!keyed_mutex_acquired(static_cast<HRESULT>(WAIT_TIMEOUT)),
|
||||
"WAIT_TIMEOUT -> not acquired (producer busy; skip this frame)");
|
||||
check(!keyed_mutex_acquired(E_FAIL), "E_FAIL -> not acquired");
|
||||
check(!keyed_mutex_acquired(DXGI_ERROR_DEVICE_REMOVED), "DEVICE_REMOVED -> not acquired");
|
||||
|
||||
std::printf(g_failures == 0 ? "PASS keyed_mutex_test\n" : "FAILED keyed_mutex_test (%d)\n", g_failures);
|
||||
return g_failures == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user