mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-11 06:51:00 +03:00
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:
@@ -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) {
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user