fix: address codex review findings on probe/watchdog fallback

- a probe or raster-stall failure record now also downgrades sessions
  that are already running: main_set_local_option broadcasts the
  fallback for failed-* health writes, which also closes the hole where
  the watchdog's idempotence guard no-oped after the probe had already
  written the record
- probe success requires a frame timing newer than the first push:
  'consumed' advances inside the plugin callback before the GL/Metal
  upload, so a raster thread hanging in the driver no longer counts as
  a pass (and cannot clear a previous failure)
- watchdog failures are tagged with the failing backend
  (failed-watchdog-rgba/gpu); the rgba-only probe clears only the rgba
  class, so a working pixel-buffer path can no longer re-enable a
  broken D3D shared-handle path every launch
- the watchdog pauses while the session's window is hidden (new
  session_set_render_visible FFI wired to the window minimize/restore
  events): a display registered in a minimized window no longer records
  a false global failure; observation now counts pushes within the
  window rather than since registration
- probe failure verdicts additionally require a resumed lifecycle,
  matching the raster-stall monitor
- linux plugin: deferred-unref grace lengthened to 10s (no raster-side
  completion barrier exists; documented as heuristic), ref bumped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rustdesk
2026-08-13 12:30:15 +08:00
parent 8fc82d04ac
commit 24a16d9a30
7 changed files with 140 additions and 24 deletions

View File

@@ -11,6 +11,7 @@ import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
import 'package:flutter_hbb/desktop/pages/view_camera_page.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart';
@@ -388,6 +389,7 @@ class _DesktopTabState extends State<DesktopTab>
void onWindowMinimize() {
stateGlobal.setMinimized(true);
stateGlobal.setMaximized(false);
_updateSessionsRenderVisible(false);
super.onWindowMinimize();
}
@@ -395,6 +397,7 @@ class _DesktopTabState extends State<DesktopTab>
void onWindowMaximize() {
stateGlobal.setMinimized(false);
_setMaximized(true);
_updateSessionsRenderVisible(true);
super.onWindowMaximize();
}
@@ -402,6 +405,7 @@ class _DesktopTabState extends State<DesktopTab>
void onWindowUnmaximize() {
stateGlobal.setMinimized(false);
_setMaximized(false);
_updateSessionsRenderVisible(true);
super.onWindowUnmaximize();
}
@@ -409,9 +413,26 @@ class _DesktopTabState extends State<DesktopTab>
void onWindowRestore() {
// A plain restore (no maximize involved) must clear the minimized flag.
stateGlobal.setMinimized(false);
_updateSessionsRenderVisible(true);
super.onWindowRestore();
}
// A hidden window composites nothing; pause the Rust-side texture watchdog
// for its sessions so it cannot record a false failure.
void _updateSessionsRenderVisible(bool visible) {
if (tabType != DesktopTabType.remoteScreen &&
tabType != DesktopTabType.viewCamera) {
return;
}
for (final tab in controller.state.value.tabs) {
try {
final ffi = Get.find<FFI>(tag: tab.key);
bind.sessionSetRenderVisible(
sessionId: ffi.sessionId, visible: visible);
} catch (_) {}
}
}
_saveFrame({bool? flush}) async {
try {
if (tabType == DesktopTabType.main) {

View File

@@ -32,6 +32,7 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
bool _sawTimings = false;
bool _wasEffectiveOn = false;
DateTime? _lastTimings;
DateTime? _firstPush;
@override
void initState() {
@@ -82,15 +83,31 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
setState(() => _textureId = id);
_timer = Timer.periodic(const Duration(milliseconds: 100), (_) {
_ticks += 1;
_firstPush ??= DateTime.now();
bind.mainPushTextureProbeFrame(ptr: _ptr);
if (bind.mainGetTextureProbeConsumed(ptr: _ptr) > 0) {
final consumed = bind.mainGetTextureProbeConsumed(ptr: _ptr) > 0;
// "Consumed" advances inside the plugin callback, before the GL/Metal
// upload; only a frame timing after the push proves a completed frame.
final frameCompleted = consumed &&
_lastTimings != null &&
_lastTimings!.isAfter(_firstPush!);
if (frameCompleted) {
_finish(true);
} else if (_ticks >= 10) {
if (consumed) {
_finish(null);
return;
}
// Only a window that is visibly compositing can prove a failure.
final lifecycle = SchedulerBinding.instance.lifecycleState;
final active =
lifecycle == null || lifecycle == AppLifecycleState.resumed;
final timingsFresh = _lastTimings != null &&
DateTime.now().difference(_lastTimings!) <
const Duration(milliseconds: 1500);
_finish(!stateGlobal.isMinimized && timingsFresh ? false : null);
_finish(!stateGlobal.isMinimized && active && timingsFresh
? false
: null);
}
});
}
@@ -102,9 +119,12 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
if (ok != null) {
final old = bind.mainGetLocalOption(key: kOptionTextureRenderHealth);
if (ok) {
// A 1x1 probe pass disproves the black-texture class, not a
// raster-stall under load; that record only clears via the toggle.
if (old != 'ok' && !old.startsWith('failed-raster-stall')) {
// This rgba probe disproves only the rgba black-texture class: gpu
// failures and raster stalls clear via the option toggle alone.
final clearable = old.isEmpty ||
old.startsWith('failed-probe') ||
old.startsWith('failed-watchdog-rgba');
if (clearable) {
bind.mainSetLocalOption(key: kOptionTextureRenderHealth, value: 'ok');
}
} else if (!old.startsWith('failed')) {

View File

@@ -1462,6 +1462,9 @@ class RustdeskImpl {
required int ptr,
dynamic hint}) {}
void sessionSetRenderVisible(
{required UuidValue sessionId, required bool visible, dynamic hint}) {}
bool mainTextureRenderProbeSupported({dynamic hint}) {
return false;
}

View File

@@ -1298,8 +1298,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "7932bf9c7f64b34b3d136ff07acd7e30dd279c34"
resolved-ref: "7932bf9c7f64b34b3d136ff07acd7e30dd279c34"
ref: "883326ddd4fb2af1484bf873b4ea856a0ac440bc"
resolved-ref: "883326ddd4fb2af1484bf873b4ea856a0ac440bc"
url: "https://github.com/rustdesk-org/flutter_texture_rgba_renderer"
source: git
version: "0.0.16"

View File

@@ -88,7 +88,7 @@ dependencies:
texture_rgba_renderer:
git:
url: https://github.com/rustdesk-org/flutter_texture_rgba_renderer
ref: 7932bf9c7f64b34b3d136ff07acd7e30dd279c34
ref: 883326ddd4fb2af1484bf873b4ea856a0ac440bc
percent_indicator: ^4.2.2
dropdown_button2: ^2.0.0
flutter_gpu_texture_renderer: