diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-10-14 17:42:56 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-10-14 17:42:56 +0000 |
| commit | eb7eaca4448b2cb5e5bf340f356ebfc7b47e532c (patch) | |
| tree | 5fd82968c3e93822783f6daddbc7f1a277434e0c | |
| parent | 1e05c7bbf5e2789201e6f6d48fbac78784c3125f (diff) | |
Stop swallowing cancellation in skipFurniture()'s crash guard
Fix 2's runCatching caught Throwable, including CancellationException.
Skipping quickly between episodes cancels PlaybackService's context-load
coroutine mid-read; the swallowed cancellation let that launch carry on
and overwrite the new episode's context with the previous one's midrolls
and skip flag, so the playhead could be moved by the wrong episode's
breaks. Narrowed to an explicit try/catch that rethrows
CancellationException and catches IOException specifically (covers
CorruptionException, a subclass) rather than a generic Exception, with
the exception logged instead of silently discarded -- keeping the
original crash-guard finding intact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
| -rw-r--r-- | app/src/main/java/org/terst/listen/data/Repositories.kt | 24 |
1 files changed, 22 insertions, 2 deletions
diff --git a/app/src/main/java/org/terst/listen/data/Repositories.kt b/app/src/main/java/org/terst/listen/data/Repositories.kt index 46731c8..8a2acb2 100644 --- a/app/src/main/java/org/terst/listen/data/Repositories.kt +++ b/app/src/main/java/org/terst/listen/data/Repositories.kt @@ -3,6 +3,7 @@ package org.terst.listen.data import android.content.Context import coil3.ImageLoader import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.first import okhttp3.OkHttpClient import org.terst.listen.server.OkHttpSkipTransport @@ -25,6 +26,7 @@ import org.terst.listen.queue.RuleApplier import org.terst.listen.work.QueuedDownloads import org.terst.listen.work.Scheduling import java.io.File +import java.io.IOException import java.util.concurrent.TimeUnit /** @@ -102,8 +104,26 @@ class AppGraph(private val context: Context) { // that is today's behaviour, and it is the fail-safe direction -- a break that // silently stops being skipped is the failure mode that matters here, so losing the // preference must not quietly let a show's own audio start playing again. - override suspend fun skipFurniture() = - runCatching { preferences.skipShowFurniture.first() }.getOrDefault(true) + // + // CancellationException is deliberately rethrown rather than caught here: this runs + // inside PlaybackService's `scope.launch { context = current.contextFor(episodeId) }`, + // and skipping quickly from one episode to the next cancels that load mid-read. A + // bare `runCatching` swallows that cancellation too, so the launch body carries on + // and overwrites the new episode's context with the previous one's -- the playhead + // can then be moved by the wrong episode's breaks. Do not "simplify" this back to + // `runCatching`; that is the regression this comment exists to prevent. + override suspend fun skipFurniture() = try { + preferences.skipShowFurniture.first() + } catch (e: CancellationException) { + throw e + } catch (e: IOException) { + // CorruptionException is a subclass of IOException, so this covers both of + // DataStore's documented failure modes without reaching for a generic Exception + // catch. Logged rather than silently discarded, so a damaged preferences file + // leaves a trace instead of disappearing into a boolean default. + android.util.Log.w("AppGraph", "skipShowFurniture unreadable, defaulting to skip", e) + true + } }) } |
