Fix WGC cleanup - capture start_free_threaded() return value
Some checks failed
Validate / validate (push) Failing after 9s

The critical bug was not capturing the return value from start_free_threaded(),
which returns a CaptureControl object with the proper wait() method.

Changes:
- Store CaptureControl from start_free_threaded() return value
- Use CaptureControl.wait() to block until thread finishes (not timeout)
- Remove redundant capture_control storage from frame callback
- Cleanup now properly releases GPU resources and yellow border

Tested: Yellow border now disappears immediately after capture test ends.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 18:43:23 +03:00
parent e2508554cd
commit 17bb4110ec

View File

@@ -92,10 +92,6 @@ class WGCEngine(CaptureEngine):
try:
logger.debug("WGC frame callback triggered")
# Store capture_control reference for cleanup
if self._capture_control is None:
self._capture_control = capture_control
# Get frame buffer as numpy array
frame_buffer = frame.frame_buffer
width = frame.width
@@ -126,8 +122,9 @@ class WGCEngine(CaptureEngine):
self._capture_instance.closed_handler = on_closed
# Start capture using free-threaded mode (non-blocking)
# IMPORTANT: start_free_threaded() returns a CaptureControl object for cleanup
logger.debug("Starting WGC capture (free-threaded mode)...")
self._capture_instance.start_free_threaded()
self._capture_control = self._capture_instance.start_free_threaded()
# Wait for first frame to arrive (with timeout)
logger.debug("Waiting for first WGC frame...")
@@ -152,48 +149,43 @@ class WGCEngine(CaptureEngine):
def cleanup(self) -> None:
"""Cleanup WGC resources."""
# For free-threaded captures, cleanup is tricky:
# 1. Stop capture via capture_control if available
# 2. Explicitly delete the capture instance (releases COM objects)
# 3. Wait for resources to be freed (give Windows time to cleanup)
# 4. Force garbage collection multiple times to ensure COM cleanup
# Proper cleanup for free-threaded captures:
# 1. Stop capture via CaptureControl.stop() (signals thread to stop)
# 2. Wait for thread to finish using CaptureControl.wait() (blocks until done)
# 3. Delete capture instance (releases COM objects)
# 4. Force garbage collection (ensures COM cleanup)
if self._capture_control:
try:
logger.debug("Stopping WGC capture session via capture_control...")
logger.debug("Stopping WGC capture thread...")
self._capture_control.stop()
logger.debug("Waiting for WGC capture thread to finish...")
# This will block until the capture thread actually finishes
# This is the CORRECT way to wait for cleanup (not a timeout!)
self._capture_control.wait()
logger.debug("WGC capture thread finished successfully")
except Exception as e:
logger.error(f"Error stopping WGC capture_control: {e}")
logger.error(f"Error during WGC capture control cleanup: {e}", exc_info=True)
finally:
self._capture_control = None
# Explicitly delete the capture instance BEFORE waiting
# This is critical for releasing COM objects and GPU resources
# Now that the thread has stopped, delete the capture instance
if self._capture_instance:
try:
logger.debug("Explicitly deleting WGC capture instance...")
logger.debug("Deleting WGC capture instance...")
instance = self._capture_instance
self._capture_instance = None
del instance
logger.debug("WGC capture instance deleted")
except Exception as e:
logger.error(f"Error releasing WGC capture instance: {e}")
logger.error(f"Error deleting WGC capture instance: {e}", exc_info=True)
self._capture_instance = None
# Force garbage collection multiple times to ensure COM cleanup
# COM objects may require multiple GC passes to fully release
logger.debug("Forcing garbage collection for COM cleanup...")
for i in range(3):
gc.collect()
time.sleep(0.05) # Small delay between GC passes
logger.debug("Waiting for WGC resources to be fully released...")
# Give Windows time to clean up the capture session and remove border
time.sleep(0.3)
# Final GC pass
# Force garbage collection to release COM objects
logger.debug("Running garbage collection for COM cleanup...")
gc.collect()
logger.debug("Final garbage collection completed")
logger.debug("Garbage collection completed")
with self._frame_lock:
self._latest_frame = None