From 99281df5fbdce1c60152da82c5bf7216bf47ed4c Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 31 Jan 2026 14:29:14 +0700 Subject: [PATCH] perf: optimize cache cleanup and reduce unnecessary widget rebuilds --- go_backend/parallel.go | 61 +++++++++++++++++++++++++++++++++------ lib/main.dart | 3 +- lib/screens/home_tab.dart | 12 +++++--- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/go_backend/parallel.go b/go_backend/parallel.go index 2481ecfe..99e9cd1e 100644 --- a/go_backend/parallel.go +++ b/go_backend/parallel.go @@ -17,6 +17,9 @@ type TrackIDCache struct { cache map[string]*TrackIDCacheEntry mu sync.RWMutex ttl time.Duration + // Cleanup is triggered on writes at a fixed interval to avoid unbounded growth. + lastCleanup time.Time + cleanupInterval time.Duration } var ( @@ -27,8 +30,9 @@ var ( func GetTrackIDCache() *TrackIDCache { trackIDCacheOnce.Do(func() { globalTrackIDCache = &TrackIDCache{ - cache: make(map[string]*TrackIDCacheEntry), - ttl: 30 * time.Minute, + cache: make(map[string]*TrackIDCacheEntry), + ttl: 30 * time.Minute, + cleanupInterval: 5 * time.Minute, } }) return globalTrackIDCache @@ -36,13 +40,34 @@ func GetTrackIDCache() *TrackIDCache { func (c *TrackIDCache) Get(isrc string) *TrackIDCacheEntry { c.mu.RLock() - defer c.mu.RUnlock() - entry, exists := c.cache[isrc] - if !exists || time.Now().After(entry.ExpiresAt) { + if !exists { + c.mu.RUnlock() return nil } - return entry + expired := time.Now().After(entry.ExpiresAt) + c.mu.RUnlock() + + if !expired { + return entry + } + + // Lazily delete expired entry. + c.mu.Lock() + entry, exists = c.cache[isrc] + if exists && time.Now().After(entry.ExpiresAt) { + delete(c.cache, isrc) + } + c.mu.Unlock() + return nil +} + +func (c *TrackIDCache) pruneExpiredLocked(now time.Time) { + for key, entry := range c.cache { + if now.After(entry.ExpiresAt) { + delete(c.cache, key) + } + } } func (c *TrackIDCache) SetTidal(isrc string, trackID int64) { @@ -55,7 +80,13 @@ func (c *TrackIDCache) SetTidal(isrc string, trackID int64) { c.cache[isrc] = entry } entry.TidalTrackID = trackID - entry.ExpiresAt = time.Now().Add(c.ttl) + now := time.Now() + entry.ExpiresAt = now.Add(c.ttl) + + if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) { + c.pruneExpiredLocked(now) + c.lastCleanup = now + } } func (c *TrackIDCache) SetQobuz(isrc string, trackID int64) { @@ -68,7 +99,13 @@ func (c *TrackIDCache) SetQobuz(isrc string, trackID int64) { c.cache[isrc] = entry } entry.QobuzTrackID = trackID - entry.ExpiresAt = time.Now().Add(c.ttl) + now := time.Now() + entry.ExpiresAt = now.Add(c.ttl) + + if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) { + c.pruneExpiredLocked(now) + c.lastCleanup = now + } } func (c *TrackIDCache) SetAmazon(isrc string, trackID string) { @@ -81,7 +118,13 @@ func (c *TrackIDCache) SetAmazon(isrc string, trackID string) { c.cache[isrc] = entry } entry.AmazonTrackID = trackID - entry.ExpiresAt = time.Now().Add(c.ttl) + now := time.Now() + entry.ExpiresAt = now.Add(c.ttl) + + if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) { + c.pruneExpiredLocked(now) + c.lastCleanup = now + } } func (c *TrackIDCache) Clear() { diff --git a/lib/main.dart b/lib/main.dart index 37602511..a1fe6993 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -43,6 +43,8 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> { void initState() { super.initState(); _initializeExtensions(); + // Trigger history provider initialization without subscribing to updates. + ref.read(downloadHistoryProvider); } Future _initializeExtensions() async { @@ -62,7 +64,6 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> { @override Widget build(BuildContext context) { - ref.watch(downloadHistoryProvider); return widget.child; } } diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 13106258..3f5b0290 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -2430,12 +2430,16 @@ class _TrackItemWithStatus extends ConsumerWidget { double thumbWidth = 56; double thumbHeight = 56; - final trackState = ref.watch(trackProvider); - final extensionId = track.source ?? trackState.searchExtensionId; + final searchExtensionId = + ref.watch(trackProvider.select((s) => s.searchExtensionId)); + final extensionId = track.source ?? searchExtensionId; if (extensionId != null && extensionId.isNotEmpty) { - final extState = ref.watch(extensionProvider); - final extension = extState.extensions.where((e) => e.id == extensionId).firstOrNull; + final extension = ref.watch( + extensionProvider.select( + (s) => s.extensions.where((e) => e.id == extensionId).firstOrNull, + ), + ); if (extension?.searchBehavior != null) { final size = extension!.searchBehavior!.getThumbnailSize(defaultSize: 56); thumbWidth = size.$1;