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.
This commit is contained in:
zarzet
2026-06-30 06:20:10 +07:00
parent d882fc292c
commit dc8bb2cbc2
3 changed files with 41 additions and 11 deletions
+6 -2
View File
@@ -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
}
+33 -3
View File
@@ -808,7 +808,9 @@ class ExtensionInstallBatchResult {
}
class ExtensionNotifier extends Notifier<ExtensionState> {
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<void>? _initializationCompleter;
@@ -970,6 +972,34 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
_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<ExtensionHealthStatus?> checkExtensionHealth(
String extensionId, {
bool force = false,
@@ -1007,7 +1037,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
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<ExtensionState> {
state.healthStatuses,
)..[extensionId] = status;
_healthExpiresAt[extensionId] = DateTime.now().add(
const Duration(seconds: 20),
_extensionHealthUnknownCacheTtl,
);
state = state.copyWith(healthStatuses: updated);
}
+2 -6
View File
@@ -75,9 +75,7 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
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<DownloadServicePicker> {
setState(() => _selectedService = extension.id);
if (extension.hasServiceHealth) {
unawaited(
ref
.read(extensionProvider.notifier)
.checkExtensionHealth(extension.id, force: true),
ref.read(extensionProvider.notifier).checkExtensionHealth(extension.id),
);
}
}