Skip to content

Harden 1.2 GPU acquisition and display reset recovery with regression tests - #5

Merged
Tiflit merged 14 commits into
mainfrom
feature/fix-logging-reset-race
Oct 22, 2026
Merged

Tiflit merged 14 commits into
mainfrom
feature/fix-logging-reset-race

Conversation

@Tiflit

@Tiflit Tiflit commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Problem and resulting behavior

GPU acquisition previously destroyed a healthy fallback before trying the preferred GPU. Display resets could also overlap, wait indefinitely without feedback, and re-enable adapters without preserving their initial disabled state.

This combined 1.2 candidate retains a healthy device until a replacement is created. A verified successful display reset now cleanly exits GPU-Switcher without relaunching, restoring the original intent of clearing GPU clients and allowing the dGPU to become inactive. It serializes cooperating reset helpers with a machine-wide named mutex, snapshots initial device state, and applies bounded recovery only to adapters successfully disabled by the current operation.

Changes

  • Resume timer creation failures are logged; the health check completes an elapsed resume delay as a fallback (up to the next 30-second check).
  • Startup validates tray registration, callback version, and the health timer before GPU acquisition; initialization failure shows an error and exits. Failed tray restoration after Explorer restarts releases the device and retries via the health check while preserving reset supervision.
  • Restores successful reset -> clean exit without relaunch. Automatic relaunch originated in 65f60ab and was carried into the previous builds. UAC cancellation and partial/failure warning behavior are retained.
  • Includes PR Bump version to 1.2.0 #4 at d4def55 and the earlier UTF-8 conversion and reset/resume exclusion fixes.
  • Preferred/current/remaining candidate ordering with temporary COM resources and fallback retention; existing classification thresholds and vendor/VRAM scores are unchanged.
  • Fresh devnode lookup before enable requests and status checks, with retries and final recovery passes. Pre-disabled, stopped, and unreadable devices are skipped.
  • Per-device initial state, request results, enable-attempt counts, last locate/status CONFIGRET, problem code, and elapsed-time diagnostics.
  • Power-transition gate blocks ordinary acquisition from suspend until the resume delay completes, coalesces duplicate wake notifications, and rearms a resume timer cancelled by reset. This addresses periodic-scan churn observed in the Legion log.
  • Nonblocking UI-timer supervision replaces the reset wait thread. After 60 seconds it warns once and keeps monitoring. Timeout or wait failure does not permit acquisition or another reset.
  • Normal Exit and --exit closure are blocked during reset supervision. A reopened tray detects an existing helper lock; its eventual result is explicitly unknown to the new tray.
  • Lock creation/inspection failures conservatively block operations.
  • View Log menu item and build commit in startup logs (unknown for builds without Git metadata).

Validation

  • Portable acquisition, reset-policy, and power-transition tests passed locally with GCC C++17, -Wall -Wextra -Werror -pedantic.
  • Acquisition tests cover ordering, replacement lifetime, fallback retention, candidate retries, enumeration omissions, empty lists, and device loss during acquisition.
  • Reset-policy tests cover timeout then completion, wait failure preserving exclusion, initial-state eligibility, untouched-device protection, transient failure, failed startup verification followed by recovery, and bounded permanent failure.
  • Windows-only lock tests launch separate processes to check exclusion while locked, admission after release, and failure with a wrong-type named object. Tests do not reset hardware.
  • Windows x64 Release build and all four CTest suites passed for the reset-to-exit correction at 8fe69f0. The corrected successful reset-to-exit path also passed the user's Legion hardware test.
  • Power-transition tests cover suspend exclusion, delayed acquisition, duplicate wake notifications, stale/early timers, repeated suspend, and rearming after reset cancels a pending timer.
  • Missing-resume-timer regression scenarios and all three portable suites passed locally for 772d591; Windows CI passed, including all four CTest suites. Win32 timer/tray failures are not injected by these tests. The user tested the PR merge artifact fe1e2a4 (tree identical to 772d591): startup, sleep/resume, and successful reset-to-exit passed. The supplied log shows one delayed resume acquisition, both adapters recovered after their first enable requests, reset Success in 7094 ms, and clean exit without relaunch. The subsequent Explorer-restart check passed by user observation: the tray icon survived restarting Windows Explorer. The additional log snapshot shows the same merge artifact starting at 14:32:59, acquiring NVIDIA at 14:33:05, releasing its device and exiting cleanly at 14:33:37. Explorer recreation is not explicitly logged, so tray survival is attributed to the user's report. The successful Explorer-restart and ordinary-Exit smoke checks are complete; injected Win32 failure paths remain untested.
  • The user additionally confirmed visually that the tray icon briefly turns yellow during startup and display switching to the dGPU, then returns to NVIDIA green. This matches the icon mapping: non-Active states use yellow, while Active on NVIDIA uses green. Tray color reports application/device state; it does not independently measure panel routing.
  • git diff --check passed.
  • Legion hardware baseline for e147067: user reported ordinary launch switching after recreating the NVIDIA App profile with Automatic Display Switching On, working sleep/resume and return to Intel on exit, UAC cancellation recovery, and successful adapter resets. Logs verify lifecycle/device state; panel routing is the user's observation. Those results predate the new power gate.
  • Legion validation checklist documents runtime steps and coverage limits.

Integration and limits

PR #5 was merged into main as be7c45d. PR #4's head is included as a merge parent in the combined implementation.

The user retested 1893b60: launch, delayed resume acquisition, clean Exit, and panel return to Intel succeeded. LockApp's continued NVIDIA use after ordinary Exit was clarified by the user as expected Windows behavior, not a GPU-Switcher defect; clearing such clients is the purpose of manual Restart Display Adapters. No independent LockApp investigation is needed.

The corrected successful reset-to-exit path at 8fe69f0 passed the user's manual test: both adapters recovered after their first enable requests, the helper reported Success in 6547 ms, and the parent exited cleanly without relaunch at 13:11:46. The user confirmed the intended behavior. Historical builds' reset/relaunch results remain recorded accurately. Failed resets may still leave an adapter unresolved. There is no persistent recovery journal for a forcibly terminated helper. Other hardware, physical failure recovery, and cross-account/cross-session UAC remain unverified. Cross-process/cross-account logging and universal GPU classification remain follow-ups. The global mutex coordinates this tool's helpers, not external device managers.

…et, and fix release tag pattern

- Distinguish discrete GPU candidates from integrated graphics; remember preferred adapter by Windows Locally Unique Identifier (LUID) to prevent trapping onto integrated GPUs on disconnect.
- Introduce explicit AppState machine (NoDiscreteGpu, Acquiring, Active, DeviceLost, WaitingForDiscreteGpu, Resetting).
- Add WM_DEVICECHANGE (DBT_DEVNODES_CHANGED) handling for instantaneous eGPU/dGPU plug-and-play re-acquisition.
- Overhaul CycleAllDisplayAdapters with ResetResult enum and CM_Get_DevNode_Status driver startup verification.
- Propagate reset exit codes to parent process to prevent relaunch loops on failure and warn user via balloon.
- Fix GitHub Actions release tag trigger to standard glob 'v*'.
- Update README for Windows 10/11 Graphics Settings alongside NVIDIA Control Panel.
… docs

- Prevent preference drift by locking g_preferredLuid to the initial discrete selection; support temporary fallback adapters and auto-restore preference on reconnection.
- Treat PartialDisable as an incomplete reset in WM_RESET_DONE with explicit balloon warning rather than silent relaunch.
- Log state transitions via SetAppState using AppStateToString.
- Clean up unused candidate vector in main.cpp.
- Tighten comments and README regarding PCI device targeting and event-driven devnodes notifications.
- Fix troubleshooting anchor link in README to #one-time-gpu-preference-setup.
…t check

Avoid tearing down and rebuilding active D3D11 fallback devices on non-GPU devnode changes or periodic timer ticks by checking IsAdapterPresent(g_preferredLuid) before attempting reacquisition.
@Tiflit Tiflit changed the title Fix UTF-8 logging and prevent GPU acquisition during reset Integrate 1.2 recovery with transactional GPU acquisition and tests Oct 22, 2026
@Tiflit Tiflit changed the title Integrate 1.2 recovery with transactional GPU acquisition and tests Harden 1.2 GPU acquisition and display reset recovery with regression tests Oct 22, 2026
@Tiflit
Tiflit marked this pull request as ready for review October 22, 2026 18:36
@Tiflit
Tiflit merged commit be7c45d into main Oct 22, 2026
2 checks passed
@Tiflit
Tiflit deleted the feature/fix-logging-reset-race branch October 22, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant