From dc8bb2cbc2723dcc6f0944463adaaaa0754995ee Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 30 Jun 2026 06:20:10 +0700 Subject: [PATCH] feat(extension-health): lengthen cache TTL and honor per-check minimum Raise default extension health cache to 10 minutes with a 1-minute floor and a shorter TTL for unknown status. Mirror the TTL rules in the Go backend and stop force-refreshing health checks from the download service picker on every open. --- go_backend/extension_health.go | 8 ++++-- lib/providers/extension_provider.dart | 36 ++++++++++++++++++++++-- lib/widgets/download_service_picker.dart | 8 ++---- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/go_backend/extension_health.go b/go_backend/extension_health.go index cf20b3fa..c005d72f 100644 --- a/go_backend/extension_health.go +++ b/go_backend/extension_health.go @@ -15,8 +15,9 @@ import ( const ( extensionHealthDefaultTimeout = 4 * time.Second extensionHealthMaxBodyBytes = 64 * 1024 - extensionHealthDefaultCache = 60 * time.Second - extensionHealthUnknownCache = 20 * time.Second + extensionHealthDefaultCache = 10 * time.Minute + extensionHealthMinCache = 60 * time.Second + extensionHealthUnknownCache = 2 * time.Minute ) type ExtensionHealthResult struct { @@ -166,6 +167,9 @@ func extensionHealthCacheTTL(checks []ExtensionHealthCheck) time.Duration { continue } checkTTL := time.Duration(check.CacheTTLSeconds) * time.Second + if checkTTL < extensionHealthMinCache { + checkTTL = extensionHealthMinCache + } if checkTTL < ttl { ttl = checkTTL } diff --git a/lib/providers/extension_provider.dart b/lib/providers/extension_provider.dart index 078d38f0..0df1b83c 100644 --- a/lib/providers/extension_provider.dart +++ b/lib/providers/extension_provider.dart @@ -808,7 +808,9 @@ class ExtensionInstallBatchResult { } class ExtensionNotifier extends Notifier { - static const _extensionHealthCacheTtl = Duration(seconds: 60); + static const _extensionHealthDefaultCacheTtl = Duration(minutes: 10); + static const _extensionHealthMinimumCacheTtl = Duration(minutes: 1); + static const _extensionHealthUnknownCacheTtl = Duration(minutes: 2); AppLifecycleListener? _appLifecycleListener; bool _cleanupInFlight = false; Completer? _initializationCompleter; @@ -970,6 +972,34 @@ class ExtensionNotifier extends Notifier { _scheduleExtensionHealthRefresh(state.extensions, force: force); } + Duration _extensionHealthCacheTtlFor(Extension extension) { + var ttl = _extensionHealthDefaultCacheTtl; + for (final check in extension.serviceHealth) { + final seconds = check.cacheTtlSeconds; + if (seconds == null || seconds <= 0) continue; + + var checkTtl = Duration(seconds: seconds); + if (checkTtl < _extensionHealthMinimumCacheTtl) { + checkTtl = _extensionHealthMinimumCacheTtl; + } + if (checkTtl < ttl) { + ttl = checkTtl; + } + } + return ttl; + } + + Duration _extensionHealthCacheTtlForStatus( + Extension extension, + String status, + ) { + final ttl = _extensionHealthCacheTtlFor(extension); + if (status == 'unknown' && ttl > _extensionHealthUnknownCacheTtl) { + return _extensionHealthUnknownCacheTtl; + } + return ttl; + } + Future checkExtensionHealth( String extensionId, { bool force = false, @@ -1007,7 +1037,7 @@ class ExtensionNotifier extends Notifier { state.healthStatuses, )..[extensionId] = status; _healthExpiresAt[extensionId] = DateTime.now().add( - _extensionHealthCacheTtl, + _extensionHealthCacheTtlForStatus(ext, status.status), ); state = state.copyWith(healthStatuses: updated); } @@ -1025,7 +1055,7 @@ class ExtensionNotifier extends Notifier { state.healthStatuses, )..[extensionId] = status; _healthExpiresAt[extensionId] = DateTime.now().add( - const Duration(seconds: 20), + _extensionHealthUnknownCacheTtl, ); state = state.copyWith(healthStatuses: updated); } diff --git a/lib/widgets/download_service_picker.dart b/lib/widgets/download_service_picker.dart index dd710508..da178e27 100644 --- a/lib/widgets/download_service_picker.dart +++ b/lib/widgets/download_service_picker.dart @@ -75,9 +75,7 @@ class _DownloadServicePickerState extends ConsumerState { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - ref - .read(extensionProvider.notifier) - .refreshEnabledExtensionHealth(force: true); + ref.read(extensionProvider.notifier).refreshEnabledExtensionHealth(); }); final downloadExtensions = _downloadExtensions(); final recommended = widget.recommendedService; @@ -109,9 +107,7 @@ class _DownloadServicePickerState extends ConsumerState { setState(() => _selectedService = extension.id); if (extension.hasServiceHealth) { unawaited( - ref - .read(extensionProvider.notifier) - .checkExtensionHealth(extension.id, force: true), + ref.read(extensionProvider.notifier).checkExtensionHealth(extension.id), ); } }