From de1362561fbf8c2f3741b4b8a2491cae45db3710 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 27 Jun 2026 12:26:44 -0400 Subject: [PATCH] fix(perf): collect debug memory snapshot off the main thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The debug-only MemoryUsageChip ("X/YMB" top-bar indicator, gated on isDebug) polls collectMemorySnapshot() every 2s from a produceState block, which runs on the main thread. That reads coil3.disk.DiskLruCache .size(), a @Synchronized call. On cold start the Coil disk cache holds that monitor for several seconds (journal init + the burst of image writes from the initial relay event flood), so the UI thread blocked inside size() — the "Loading account" frame couldn't repaint until it returned. Profiling showed a single ~8s render frame and the UI thread "blocking from coil3.disk.DiskLruCache.size()". Collect the snapshot via withContext(Dispatchers.IO) so the synchronized read blocks a background thread instead of the UI. The "Loading account" stall on cold start drops from ~15-20s to ~5s. Debug-only path, so this never affected release builds. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../amethyst/ui/navigation/topbars/MemoryUsageChip.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/MemoryUsageChip.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/MemoryUsageChip.kt index 82b6e42ab1..85a205c84e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/MemoryUsageChip.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/MemoryUsageChip.kt @@ -41,7 +41,9 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.MemorySnapshot import com.vitorpamplona.amethyst.collectMemorySnapshot import com.vitorpamplona.amethyst.isDebug +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext @Composable fun MemoryUsageChip() { @@ -52,7 +54,12 @@ fun MemoryUsageChip() { val snapshot by produceState(null) { while (true) { - value = collectMemorySnapshot(context) + // collectMemorySnapshot reads coil3.disk.DiskLruCache.size(), which is @Synchronized and + // contends with the disk cache's own journal I/O. On cold start that lock is held by a + // background worker for seconds (initial journal read + the burst of image writes), so + // running this on the produceState default (main) dispatcher froze the UI thread — + // the "Loading account" frame couldn't repaint until size() returned. Collect off-main. + value = withContext(Dispatchers.IO) { collectMemorySnapshot(context) } delay(2_000) } }