fix(windows): recover failed display handoffs

This commit is contained in:
edde746
2026-07-25 17:38:09 +02:00
parent 6710c87892
commit 1ae58f676b
2 changed files with 67 additions and 2 deletions
+8 -2
View File
@@ -1142,13 +1142,17 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_
const bool restored = const bool restored =
backend.IsDevicePresent(mode_device_name) && backend.RestoreMode(mode_device_name, width, height, refresh_rate); backend.IsDevicePresent(mode_device_name) && backend.RestoreMode(mode_device_name, width, height, refresh_rate);
if (mode_handoff_pending) { if (mode_handoff_pending) {
// Restore the replacement first and the pre-handoff original last. Both // Restore the replacement first and the pre-handoff original last. If
// remain marked until both idempotent restores and cleanup succeed. // either restore or cleanup fails, collapse authority back to the prior
// slot so topology retries do not keep forcing the surviving replacement.
const bool previous_restored = const bool previous_restored =
backend.IsDevicePresent(previous_mode_device_name) && backend.IsDevicePresent(previous_mode_device_name) &&
backend.RestoreMode(previous_mode_device_name, previous_width, previous_height, previous_refresh_rate); backend.RestoreMode(previous_mode_device_name, previous_width, previous_height, previous_refresh_rate);
if (!restored || !previous_restored || if (!restored || !previous_restored ||
!CompleteRecoveryOperation(backend, kRegModeChanged, kRegModeTakeoverEligible, kRegModeHandoffPending)) { !CompleteRecoveryOperation(backend, kRegModeChanged, kRegModeTakeoverEligible, kRegModeHandoffPending)) {
RollbackRecoveryHandoff(
backend, kRegModeTakeoverEligible, kRegModeRecoverySlot, kRegModePreviousRecoverySlot,
kRegModeHandoffPending);
completed = false; completed = false;
} }
} else if ( } else if (
@@ -1170,6 +1174,8 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_
backend.RestoreHDR(previous_hdr_device_name, previous_original_hdr != 0); backend.RestoreHDR(previous_hdr_device_name, previous_original_hdr != 0);
if (!restored || !previous_restored || if (!restored || !previous_restored ||
!CompleteRecoveryOperation(backend, kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRHandoffPending)) { !CompleteRecoveryOperation(backend, kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRHandoffPending)) {
RollbackRecoveryHandoff(
backend, kRegHDRTakeoverEligible, kRegHDRRecoverySlot, kRegHDRPreviousRecoverySlot, kRegHDRHandoffPending);
completed = false; completed = false;
} }
} else if ( } else if (
@@ -699,6 +699,64 @@ void TestCrashDuringTakeoverHandoffRecoversBothOriginals() {
"HDR handoff recovery must restore replacement then prior and preserve its sibling"); "HDR handoff recovery must restore replacement then prior and preserve its sibling");
} }
void TestFailedHandoffRecoveryRollsBackForTakeover() {
FakeRecoveryBackend mode;
mode.SeedBoth();
mode.dwords[kModeTakeoverEligible] = 1;
mode.dwords[kHDRTakeoverEligible] = 1;
Check(
DisplayModeManager::PrepareModeRecovery(mode, kReplacementModeDevice, 2560, 1440, 120),
"mode takeover preparation must reach its pre-apply handoff");
mode.expected_mode_restores = {{kReplacementModeDevice, 2560, 1440, 120}};
mode.device_present[kModeDevice] = false;
Check(
!DisplayModeManager::RecoverIfNeededForTesting(mode, false, true),
"a missing prior mode display must leave handoff recovery incomplete");
Check(
mode.mode_restore_attempts == 1 && mode.dwords[kModeChanged] == 1 && mode.dwords[kModeRecoverySlot] == 0 &&
mode.dwords[kModeTakeoverEligible] == 1 && mode.dwords[kModeHandoffPending] == 0,
"failed mode handoff recovery must roll authority back to a takeover-eligible prior slot");
mode.events.clear();
Check(
!DisplayModeManager::RecoverIfNeededForTesting(mode, false, true),
"the absent prior mode display must remain retryable after handoff rollback");
Check(
EventIndex(mode.events, L"restore:mode") == mode.events.size(),
"topology retries must not keep restoring the surviving replacement display");
Check(
ApplyModeAfterPreparing(mode, kReplacementModeDevice, 2560, 1440, 120),
"a later mode request must be able to consume the rolled-back handoff");
FakeRecoveryBackend hdr;
hdr.SeedBoth();
hdr.dwords[kModeTakeoverEligible] = 1;
hdr.dwords[kHDRTakeoverEligible] = 1;
Check(
DisplayModeManager::PrepareHDRRecovery(hdr, kReplacementHDRDevice, true),
"HDR takeover preparation must reach its pre-apply handoff");
hdr.expected_hdr_restores = {{kReplacementHDRDevice, true}};
hdr.device_present[kHDRDevice] = false;
Check(
!DisplayModeManager::RecoverIfNeededForTesting(hdr, true, false),
"a missing prior HDR display must leave handoff recovery incomplete");
Check(
hdr.hdr_restore_attempts == 1 && hdr.dwords[kHDRChanged] == 1 && hdr.dwords[kHDRRecoverySlot] == 0 &&
hdr.dwords[kHDRTakeoverEligible] == 1 && hdr.dwords[kHDRHandoffPending] == 0,
"failed HDR handoff recovery must roll authority back to a takeover-eligible prior slot");
hdr.events.clear();
Check(
!DisplayModeManager::RecoverIfNeededForTesting(hdr, true, false),
"the absent prior HDR display must remain retryable after handoff rollback");
Check(
EventIndex(hdr.events, L"restore:hdr") == hdr.events.size(),
"topology retries must not keep restoring the surviving replacement HDR state");
Check(
ApplyHDRAfterPreparing(hdr, kReplacementHDRDevice, true),
"a later HDR request must be able to consume the rolled-back handoff");
}
void TestHDRReconnectRecoversBeforeTakeover() { void TestHDRReconnectRecoversBeforeTakeover() {
FakeRecoveryBackend backend; FakeRecoveryBackend backend;
backend.SeedBoth(); backend.SeedBoth();
@@ -1051,6 +1109,7 @@ int main() {
mpv::TestBadModeHDRRestoreAllowsSameKindTakeover(); mpv::TestBadModeHDRRestoreAllowsSameKindTakeover();
mpv::TestFailedOSApplyRollsBackTakeover(); mpv::TestFailedOSApplyRollsBackTakeover();
mpv::TestCrashDuringTakeoverHandoffRecoversBothOriginals(); mpv::TestCrashDuringTakeoverHandoffRecoversBothOriginals();
mpv::TestFailedHandoffRecoveryRollsBackForTakeover();
mpv::TestHDRReconnectRecoversBeforeTakeover(); mpv::TestHDRReconnectRecoversBeforeTakeover();
mpv::TestEligibleSameOriginalReuseResetsDisposition(); mpv::TestEligibleSameOriginalReuseResetsDisposition();
mpv::TestRepeatedModeTakeoverStagesOnlyTheInactiveSlot(); mpv::TestRepeatedModeTakeoverStagesOnlyTheInactiveSlot();