feat(android): bound MetadataProvider on-miss fetches to 4 concurrent

This commit is contained in:
2026-05-28 18:26:13 -04:00
parent 5b5bff767a
commit b2512fff4b
2 changed files with 77 additions and 1 deletions
@@ -15,10 +15,14 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
private const val MAX_CONCURRENT_FETCHES = 4
/**
* Cache-first metadata reads with eager on-miss live-fetch.
*
@@ -49,6 +53,12 @@ class MetadataProvider @Inject constructor(
private val inFlightArtists = ConcurrentHashMap<String, Job>()
private val inFlightTracks = ConcurrentHashMap<String, Job>()
// Caps concurrent on-miss fetches so a cold Home (~30 tiles) doesn't
// fire ~30 parallel /api calls. Mirrors Flutter HydrationQueue's
// _maxConcurrent = 4. Per-id dedup (above) is orthogonal: the gate
// bounds distinct-id fetches; extras suspend on the semaphore.
private val fetchGate = Semaphore(MAX_CONCURRENT_FETCHES)
/**
* Live AlbumRef? for [id]. Fires a background fetch the first
* time the cache emits null. Safe to subscribe concurrently from
@@ -98,7 +108,7 @@ class MetadataProvider @Inject constructor(
): Job = inFlight.computeIfAbsent(id) {
appScope.launch {
try {
block()
fetchGate.withPermit { block() }
} catch (
@Suppress("TooGenericExceptionCaught", "SwallowedException") e: Throwable,
) {