fix: reduce image cache budgets and add memory pressure handling
This commit is contained in:
+41
-5
@@ -2,7 +2,7 @@ import 'dart:async';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform, ProcessInfo;
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
@@ -109,9 +109,14 @@ Future<void> _bootstrapApp() async {
|
|||||||
// Needed for formatting dates in different locales
|
// Needed for formatting dates in different locales
|
||||||
await initializeDateFormatting(savedLocale.languageCode, null);
|
await initializeDateFormatting(savedLocale.languageCode, null);
|
||||||
|
|
||||||
// Configure image cache for large libraries
|
// Configure image cache — keep budget modest to leave headroom for Skia decode buffers
|
||||||
PaintingBinding.instance.imageCache.maximumSize = 2000; // default 1000
|
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 300 << 20; // 300MB
|
PaintingBinding.instance.imageCache.maximumSize = 1000;
|
||||||
|
PaintingBinding.instance.imageCache.maximumSizeBytes = 150 << 20; // 150MB
|
||||||
|
} else {
|
||||||
|
PaintingBinding.instance.imageCache.maximumSize = 800;
|
||||||
|
PaintingBinding.instance.imageCache.maximumSizeBytes = 100 << 20; // 100MB
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize services in parallel where possible
|
// Initialize services in parallel where possible
|
||||||
final futures = <Future<void>>[];
|
final futures = <Future<void>>[];
|
||||||
@@ -309,11 +314,25 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
|||||||
/// Last time server health probes ran from a resume event (cooldown for desktop)
|
/// Last time server health probes ran from a resume event (cooldown for desktop)
|
||||||
DateTime _lastResumeProbe = DateTime(0);
|
DateTime _lastResumeProbe = DateTime(0);
|
||||||
|
|
||||||
|
/// Periodic memory check timer for desktop platforms
|
||||||
|
Timer? _memoryCheckTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
|
// On desktop, periodically check RSS and evict image cache if too high
|
||||||
|
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||||
|
_memoryCheckTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||||
|
final rss = ProcessInfo.currentRss;
|
||||||
|
if (rss > 1536 * 1024 * 1024) { // 1.5GB
|
||||||
|
appLogger.w('RSS high ($rss bytes), evicting image caches');
|
||||||
|
_evictImageCaches();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_serverManager = MultiServerManager();
|
_serverManager = MultiServerManager();
|
||||||
_aggregationService = DataAggregationService(_serverManager);
|
_aggregationService = DataAggregationService(_serverManager);
|
||||||
_appDatabase = AppDatabase();
|
_appDatabase = AppDatabase();
|
||||||
@@ -332,10 +351,23 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_memoryCheckTimer?.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didHaveMemoryPressure() {
|
||||||
|
super.didHaveMemoryPressure();
|
||||||
|
appLogger.w('System memory pressure, evicting image caches');
|
||||||
|
_evictImageCaches();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _evictImageCaches() {
|
||||||
|
PaintingBinding.instance.imageCache.clear();
|
||||||
|
PaintingBinding.instance.imageCache.clearLiveImages();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@@ -357,8 +389,12 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
case AppLifecycleState.paused:
|
case AppLifecycleState.paused:
|
||||||
case AppLifecycleState.detached:
|
case AppLifecycleState.detached:
|
||||||
// App went to background or is closing - end session
|
|
||||||
InAppReviewService.instance.endSession();
|
InAppReviewService.instance.endSession();
|
||||||
|
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||||
|
if (ProcessInfo.currentRss > 1024 * 1024 * 1024) { // 1GB
|
||||||
|
_evictImageCaches();
|
||||||
|
}
|
||||||
|
}
|
||||||
case AppLifecycleState.inactive:
|
case AppLifecycleState.inactive:
|
||||||
case AppLifecycleState.hidden:
|
case AppLifecycleState.hidden:
|
||||||
// Transitional states - don't trigger session events
|
// Transitional states - don't trigger session events
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ class BifThumbnailService {
|
|||||||
try {
|
try {
|
||||||
final bytes = await client.downloadBifFile(partId);
|
final bytes = await client.downloadBifFile(partId);
|
||||||
if (bytes == null || bytes.isEmpty) return;
|
if (bytes == null || bytes.isEmpty) return;
|
||||||
|
if (bytes.length > 50 * 1024 * 1024) {
|
||||||
|
appLogger.w('BIF file too large (${bytes.length} bytes), skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
_entries = await Isolate.run(() => _parseBifBytes(bytes));
|
_entries = await Isolate.run(() => _parseBifBytes(bytes));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.w('BIF download/parse failed', error: e);
|
appLogger.w('BIF download/parse failed', error: e);
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ class PlexImageCacheManager extends CacheManager with ImageCacheManager {
|
|||||||
: super(
|
: super(
|
||||||
Config(
|
Config(
|
||||||
_key,
|
_key,
|
||||||
stalePeriod: const Duration(days: 30),
|
stalePeriod: const Duration(days: 14),
|
||||||
maxNrOfCacheObjects: 5000,
|
maxNrOfCacheObjects: 3000,
|
||||||
fileService: HttpFileService(
|
fileService: HttpFileService(
|
||||||
httpClient: IOClient(
|
httpClient: IOClient(
|
||||||
HttpClient()..maxConnectionsPerHost = 6,
|
HttpClient()..maxConnectionsPerHost = 6,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../models/hotkey_model.dart';
|
import '../models/hotkey_model.dart';
|
||||||
|
import 'image_cache_service.dart';
|
||||||
import 'package:plezy/utils/app_logger.dart';
|
import 'package:plezy/utils/app_logger.dart';
|
||||||
import '../i18n/strings.g.dart';
|
import '../i18n/strings.g.dart';
|
||||||
import '../models/mpv_config_models.dart';
|
import '../models/mpv_config_models.dart';
|
||||||
@@ -1386,11 +1388,9 @@ class SettingsService extends BaseSharedPreferencesService {
|
|||||||
|
|
||||||
// Clear cache (for storage cleanup)
|
// Clear cache (for storage cleanup)
|
||||||
Future<void> clearCache() async {
|
Future<void> clearCache() async {
|
||||||
// This would be expanded to clear various cache directories
|
PaintingBinding.instance.imageCache.clear();
|
||||||
// For now, we'll just clear any cache-related preferences
|
PaintingBinding.instance.imageCache.clearLiveImages();
|
||||||
await Future.wait([
|
await PlexImageCacheManager.instance.emptyCache();
|
||||||
// Add cache clearing logic here
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all settings as a map for debugging/export
|
// Get all settings as a map for debugging/export
|
||||||
|
|||||||
Reference in New Issue
Block a user