fix(memory): release retired runtimes and idle download heap

This commit is contained in:
zarzet
2026-09-07 16:28:47 +07:00
parent c48406f5d0
commit e3f7817b29
4 changed files with 48 additions and 0 deletions
@@ -1395,6 +1395,7 @@ class DownloadService : Service() {
failed = counts.failed,
)
currentStatus = "finalizing"
releaseIdleDownloadMemory()
writeNativeWorkerSnapshot(
isRunning = false,
isPaused = false,
@@ -1702,6 +1703,7 @@ class DownloadService : Service() {
failed = counts.failed,
)
currentStatus = "finalizing"
releaseIdleDownloadMemory()
writeNativeWorkerSnapshot(
isRunning = false,
isPaused = false,
@@ -1718,6 +1720,16 @@ class DownloadService : Service() {
}
}
private fun releaseIdleDownloadMemory() {
try {
// All workers and album tagging have finished. Return unused Go
// heap without forcing a collection between individual tracks.
Gobackend.releaseMemory()
} catch (e: Exception) {
android.util.Log.w("SpotiFLAC", "Failed to release idle download memory: ${e.message}")
}
}
@Synchronized
private fun ensureWakeLock() {
val existingWakeLock = wakeLock
+3
View File
@@ -201,6 +201,9 @@ func acquireIsolatedExtensionRuntime(ext *loadedExtension) (*goja.Runtime, *exte
ext.isolatedPoolMu.Lock()
if n := len(ext.isolatedPool); n > 0 {
handle := ext.isolatedPool[n-1]
// Reslicing alone retains this handle in the backing array. If the
// borrowed VM is retired after an error, its heap would stay alive.
ext.isolatedPool[n-1] = nil
ext.isolatedPool = ext.isolatedPool[:n-1]
ext.isolatedPoolMu.Unlock()
return handle.vm, handle.runtime, nil
+29
View File
@@ -1,10 +1,39 @@
package gobackend
import (
"runtime"
"testing"
"time"
"weak"
)
func TestDiscardedPooledRuntimeCanBeCollected(t *testing.T) {
ext := newTestLoadedExtension(t, ExtensionTypeDownloadProvider)
discarded := func() weak.Pointer[extensionRuntime] {
vm, rt, err := acquireIsolatedExtensionRuntime(ext)
if err != nil {
t.Fatal(err)
}
if err := vm.Set("temporaryDownloadBytes", vm.NewArrayBuffer(make([]byte, 16<<20))); err != nil {
t.Fatal(err)
}
releaseIsolatedExtensionRuntime(ext, vm, rt, true, true, nil)
vm, rt, err = acquireIsolatedExtensionRuntime(ext)
if err != nil {
t.Fatal(err)
}
pointer := weak.Make(rt)
// An operation error retires the borrowed VM instead of pooling it.
releaseIsolatedExtensionRuntime(ext, vm, rt, false, true, nil)
return pointer
}()
runtime.GC()
if discarded.Value() != nil {
t.Fatal("retired runtime and its download buffer remain reachable from the idle pool")
}
runtime.KeepAlive(ext)
}
func TestReleaseMemoryUnderPressureClearsDisposableCaches(t *testing.T) {
clearCoverMemoryCache()
coverCachePut("https://example.com/cover.jpg", []byte("cover"))
@@ -1893,6 +1893,10 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_downloadCount = 0;
}
if (!stoppedWhilePaused) {
await PlatformBridge.releaseNativeMemory();
}
_log.i(
'Queue stats - completed: $_completedInSession, failed: $_failedInSession, totalAtStart: $_totalQueuedAtStart',
);