mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
perf(memory): drop disposable caches on pressure
This commit is contained in:
@@ -3147,6 +3147,12 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"releaseMemoryUnderPressure" -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
Gobackend.releaseMemoryUnderPressure()
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"setMetadataLanguage" -> {
|
||||
val tag = call.argument<String>("tag") ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -87,6 +87,13 @@ var (
|
||||
coverFetch = fetchCoverBytes
|
||||
)
|
||||
|
||||
func clearCoverMemoryCache() {
|
||||
coverMu.Lock()
|
||||
coverCache = map[string]*coverCacheEntry{}
|
||||
coverCacheBytes = 0
|
||||
coverMu.Unlock()
|
||||
}
|
||||
|
||||
// fetchCoverCached returns cover bytes for a final URL, collapsing concurrent
|
||||
// requests for the same URL into a single fetch (singleflight) and caching
|
||||
// results in memory for the duration of an album batch. The returned slice is
|
||||
|
||||
@@ -36,7 +36,25 @@ func metadataAcceptLanguage() string {
|
||||
// when backgrounded, so the Go side's RSS doesn't sit at its high-water mark
|
||||
// after large downloads/tag writes.
|
||||
func ReleaseMemory() {
|
||||
releaseMemory(false)
|
||||
}
|
||||
|
||||
// ReleaseMemoryUnderPressure additionally drops disposable live caches. It is
|
||||
// reserved for an OS memory-pressure signal; ordinary backgrounding keeps
|
||||
// network-backed caches warm.
|
||||
func ReleaseMemoryUnderPressure() {
|
||||
releaseMemory(true)
|
||||
}
|
||||
|
||||
func releaseMemory(underPressure bool) {
|
||||
drainAllIsolatedRuntimePools()
|
||||
CloseIdleConnections()
|
||||
if underPressure {
|
||||
clearCoverMemoryCache()
|
||||
globalLyricsCache.ClearAll()
|
||||
clearPrivateIPCache()
|
||||
clearExtensionHealthCache()
|
||||
}
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ var (
|
||||
extensionHealthCache = map[string]cachedExtensionHealthResult{}
|
||||
)
|
||||
|
||||
func clearExtensionHealthCache() {
|
||||
extensionHealthCacheMu.Lock()
|
||||
extensionHealthCache = map[string]cachedExtensionHealthResult{}
|
||||
extensionHealthCacheMu.Unlock()
|
||||
}
|
||||
|
||||
func CheckExtensionHealthJSON(extensionID string) (string, error) {
|
||||
manager := getExtensionManager()
|
||||
ext, err := manager.GetExtension(extensionID)
|
||||
|
||||
@@ -181,6 +181,12 @@ var (
|
||||
privateIPCacheMu sync.RWMutex
|
||||
)
|
||||
|
||||
func clearPrivateIPCache() {
|
||||
privateIPCacheMu.Lock()
|
||||
privateIPCache = make(map[string]privateIPCacheEntry)
|
||||
privateIPCacheMu.Unlock()
|
||||
}
|
||||
|
||||
func newExtensionRuntime(ext *loadedExtension) *extensionRuntime {
|
||||
jar, _ := newSimpleCookieJar()
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package gobackend
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestReleaseMemoryUnderPressureClearsDisposableCaches(t *testing.T) {
|
||||
clearCoverMemoryCache()
|
||||
coverCachePut("https://example.com/cover.jpg", []byte("cover"))
|
||||
globalLyricsCache.ClearAll()
|
||||
globalLyricsCache.Set("artist", "track", 120, &LyricsResponse{PlainLyrics: "lyrics"})
|
||||
|
||||
privateIPCacheMu.Lock()
|
||||
privateIPCache["example.com"] = privateIPCacheEntry{expiresAt: time.Now().Add(time.Hour)}
|
||||
privateIPCacheMu.Unlock()
|
||||
extensionHealthCacheMu.Lock()
|
||||
extensionHealthCache["extension"] = cachedExtensionHealthResult{expiresAt: time.Now().Add(time.Hour)}
|
||||
extensionHealthCacheMu.Unlock()
|
||||
|
||||
ReleaseMemoryUnderPressure()
|
||||
|
||||
coverMu.Lock()
|
||||
coverEntries, coverBytes := len(coverCache), coverCacheBytes
|
||||
coverMu.Unlock()
|
||||
if coverEntries != 0 || coverBytes != 0 {
|
||||
t.Fatalf("cover cache retained %d entries/%d bytes", coverEntries, coverBytes)
|
||||
}
|
||||
if globalLyricsCache.Size() != 0 {
|
||||
t.Fatalf("lyrics cache retained %d entries", globalLyricsCache.Size())
|
||||
}
|
||||
privateIPCacheMu.RLock()
|
||||
privateEntries := len(privateIPCache)
|
||||
privateIPCacheMu.RUnlock()
|
||||
if privateEntries != 0 {
|
||||
t.Fatalf("private IP cache retained %d entries", privateEntries)
|
||||
}
|
||||
extensionHealthCacheMu.Lock()
|
||||
healthEntries := len(extensionHealthCache)
|
||||
extensionHealthCacheMu.Unlock()
|
||||
if healthEntries != 0 {
|
||||
t.Fatalf("health cache retained %d entries", healthEntries)
|
||||
}
|
||||
}
|
||||
@@ -733,6 +733,14 @@ import Gobackend
|
||||
case "clearLogs":
|
||||
GobackendClearLogs()
|
||||
return nil
|
||||
|
||||
case "releaseMemory":
|
||||
GobackendReleaseMemory()
|
||||
return nil
|
||||
|
||||
case "releaseMemoryUnderPressure":
|
||||
GobackendReleaseMemoryUnderPressure()
|
||||
return nil
|
||||
|
||||
case "getLogCount":
|
||||
let response = GobackendGetLogCount()
|
||||
|
||||
+1
-1
@@ -229,7 +229,7 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization>
|
||||
if (CoverCacheManager.isInitialized) {
|
||||
CoverCacheManager.instance.store.emptyMemoryCache();
|
||||
}
|
||||
unawaited(PlatformBridge.releaseNativeMemory());
|
||||
unawaited(PlatformBridge.releaseNativeMemory(underPressure: true));
|
||||
}
|
||||
|
||||
void _initializeDeferredProviders() {
|
||||
|
||||
@@ -1168,9 +1168,11 @@ class PlatformBridge {
|
||||
|
||||
/// Ask the Go backend to GC and return freed heap to the OS. Best-effort:
|
||||
/// safe to call on memory pressure or when the app is backgrounded.
|
||||
static Future<void> releaseNativeMemory() async {
|
||||
static Future<void> releaseNativeMemory({bool underPressure = false}) async {
|
||||
try {
|
||||
await _channel.invokeMethod('releaseMemory');
|
||||
await _channel.invokeMethod(
|
||||
underPressure ? 'releaseMemoryUnderPressure' : 'releaseMemory',
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user