fix(coroutines): propagate cancellation in remote source and status view model
RemoteSource's five suspend functions and SatStatusViewModel's two fetch paths caught bare Exception, which also swallows CancellationException. When the owning scope is cancelled (screen leaves, app closes) a cancelled network call was reported as a null/error result instead of stopping: the caller kept running until the next suspension point, and SatStatusViewModel wrote state updates into an already-cancelled scope. Correct coroutine hygiene is to let cancellation propagate - rethrow CancellationException before the generic catch. Verified semantically with an asyncio probe: a swallowed cancel returns a normal-looking null and the caller continues; a propagated cancel stops the coroutine immediately. No behaviour change for real errors; :core:data and :feature:status compile.
This commit is contained in:
1 parent
baf2a7022d
commit
7319cf8f5b
2 files changed
+16
No files matched your search
@@ -20,6 +20,7 @@ package com.rtbishop.look4sat.core.data.source
|
||||
import android.content.ContentResolver
|
||||
import androidx.core.net.toUri
|
||||
import com.rtbishop.look4sat.core.domain.source.IRemoteSource
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -37,6 +38,8 @@ class RemoteSource(
|
||||
try {
|
||||
val fileUri = uri.toUri()
|
||||
contentResolver.openInputStream(fileUri)?.buffered()
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
println("RemoteSource file stream exception: $exception")
|
||||
null
|
||||
@@ -53,6 +56,8 @@ class RemoteSource(
|
||||
if (!response.isSuccessful) return@use null
|
||||
response.body?.string()
|
||||
}
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
println("RemoteSource amsat status exception: $exception")
|
||||
null
|
||||
@@ -66,6 +71,8 @@ class RemoteSource(
|
||||
if (!response.isSuccessful) return@withContext null
|
||||
ByteArrayInputStream(response.body.bytes())
|
||||
}
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
println("RemoteSource network stream exception: $exception")
|
||||
null
|
||||
@@ -82,6 +89,8 @@ class RemoteSource(
|
||||
if (!response.isSuccessful) return@use null
|
||||
response.body?.string()
|
||||
}
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
println("RemoteSource amsat catalog exception: $exception")
|
||||
null
|
||||
@@ -98,6 +107,8 @@ class RemoteSource(
|
||||
if (!response.isSuccessful) return@use null
|
||||
response.body?.string()
|
||||
}
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
println("RemoteSource amsat reports exception: $exception")
|
||||
null
|
||||
|
||||
+5
@@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.core.domain.model.SatReport
|
||||
import com.rtbishop.look4sat.core.domain.model.SatStatus
|
||||
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
@@ -50,6 +51,8 @@ class SatStatusViewModel(
|
||||
it.copy(isLoading = false, isRefreshing = false, error = "fetch_failed")
|
||||
}
|
||||
}
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
_uiState.update {
|
||||
it.copy(isLoading = false, isRefreshing = false, error = exception.message ?: "fetch_failed")
|
||||
@@ -76,6 +79,8 @@ class SatStatusViewModel(
|
||||
} else {
|
||||
_uiState.update { it.copy(isRefreshing = false, error = "fetch_failed") }
|
||||
}
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
_uiState.update { it.copy(isRefreshing = false, error = exception.message ?: "fetch_failed") }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user