perf(download): reduce progress wakeups and harden retries

This commit is contained in:
zarzet
2026-08-29 23:47:58 +07:00
parent fd74240c24
commit f1fb01604d
12 changed files with 234 additions and 51 deletions
@@ -374,6 +374,7 @@ class DownloadService : Service() {
@Volatile private var nativeWorkerCancelRequested = false
internal var nativeWorkerDownloadNetworkMode = "any"
internal var nativeWorkerNetworkCallback: ConnectivityManager.NetworkCallback? = null
internal var nativeWorkerNetworkFallbackJob: Job? = null
internal val nativeWorkerWifiNetworks = mutableSetOf<Network>()
// Bumped every time a new native queue replaces the current one. A worker
// coroutine that observes a different generation than its own must stop
@@ -94,6 +94,17 @@ internal fun DownloadService.configureNativeWorkerNetworkPolicy(settingsJson: St
"DownloadService",
"Failed to monitor Wi-Fi for native worker: ${e.message}",
)
// Callback registration can fail when the process/system callback quota is
// exhausted. Keep the Wi-Fi-only queue observable instead of leaving it
// paused forever with no event capable of resuming it.
nativeWorkerNetworkFallbackJob?.cancel()
nativeWorkerNetworkFallbackJob = serviceScope.launch {
while (NativeWorkerPolicy.requiresWifi(nativeWorkerDownloadNetworkMode)) {
delay(5_000)
refreshNativeWorkerNetworkPause()
if (nativeWorkerJob?.isActive != true) break
}
}
}
}
@@ -159,6 +170,8 @@ internal fun DownloadService.refreshNativeWorkerNetworkPause() {
}
internal fun DownloadService.unregisterNativeWorkerNetworkCallback() {
nativeWorkerNetworkFallbackJob?.cancel()
nativeWorkerNetworkFallbackJob = null
val callback = nativeWorkerNetworkCallback
nativeWorkerNetworkCallback = null
synchronized(nativeWorkerWifiNetworks) {
@@ -329,7 +329,7 @@ internal fun DownloadService.cancelNativeWorkerProgressCoordinator() {
private fun DownloadService.pollNativeWorkerProgress(generation: Long): Set<String> {
val sinceSeq = synchronized(nativeWorkerProgressLock) { nativeWorkerProgressSeq }
val raw = try {
Gobackend.getAllDownloadProgressDelta(sinceSeq)
Gobackend.waitForAllDownloadProgressDelta(sinceSeq, 5_000L)
} catch (_: Exception) {
return emptySet()
}
@@ -66,7 +66,6 @@ class MainActivity: FlutterFragmentActivity() {
"com.zarz.spotiflac/download_progress_stream"
private val LIBRARY_SCAN_PROGRESS_STREAM_CHANNEL =
"com.zarz.spotiflac/library_scan_progress_stream"
private val DOWNLOAD_PROGRESS_STREAM_POLLING_INTERVAL_MS = 1200L
// A progress bar can't show sub-second granularity; 400ms halves the
// disk-read wakeups during a scan vs the previous 200ms.
private val LIBRARY_SCAN_PROGRESS_STREAM_POLLING_INTERVAL_MS = 400L
@@ -482,12 +481,17 @@ class MainActivity: FlutterFragmentActivity() {
while (isActive && downloadProgressEventSink === sink) {
try {
val payload = withContext(Dispatchers.IO) {
Gobackend.getAllDownloadProgressDelta(lastDownloadProgressSeq)
Gobackend.waitForAllDownloadProgressDelta(
lastDownloadProgressSeq,
15_000L,
)
}
if (!isActive || downloadProgressEventSink !== sink) break
if (payload.isNotEmpty() && payload != lastDownloadProgressPayload) {
updateDownloadProgressSeq(payload)
lastDownloadProgressPayload = payload
sink.success(parseJsonPayload(payload))
delay(250L)
}
} catch (e: Exception) {
android.util.Log.w(
@@ -495,7 +499,7 @@ class MainActivity: FlutterFragmentActivity() {
"Download progress stream poll failed: ${e.message}",
)
}
delay(DOWNLOAD_PROGRESS_STREAM_POLLING_INTERVAL_MS)
if (downloadProgressEventSink !== sink) break
}
}
}
@@ -517,6 +521,7 @@ class MainActivity: FlutterFragmentActivity() {
val initialPayload = withContext(Dispatchers.IO) {
readLibraryScanProgressJsonForStream()
}
if (!isActive || libraryScanProgressEventSink !== sink) return@launch
lastLibraryScanProgressPayload = initialPayload
sink.success(parseJsonPayload(initialPayload))
} catch (e: Exception) {
@@ -530,6 +535,7 @@ class MainActivity: FlutterFragmentActivity() {
val payload = withContext(Dispatchers.IO) {
readLibraryScanProgressJsonForStream()
}
if (!isActive || libraryScanProgressEventSink !== sink) break
if (payload != lastLibraryScanProgressPayload) {
lastLibraryScanProgressPayload = payload
sink.success(parseJsonPayload(payload))