fix: ignore CancellationException in suspend functions (#1794)

* fix: ignore `CancellationException` in suspend functions

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* build(coroutine-utils): use `api` instead of `implementation`

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Aditya Wasan 2022-03-23 18:18:06 +05:30 committed by GitHub
parent cf3ae17b84
commit 9c9616d047
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 7 deletions

View file

@ -10,4 +10,5 @@ plugins {
dependencies { dependencies {
implementation(libs.kotlin.coroutines.core) implementation(libs.kotlin.coroutines.core)
implementation(libs.dagger.hilt.core) implementation(libs.dagger.hilt.core)
api(libs.thirdparty.kotlinResult)
} }

View file

@ -0,0 +1,48 @@
@file:OptIn(ExperimentalContracts::class)
@file:Suppress("RedundantSuspendModifier")
package dev.msfjarvis.aps.util.coroutines
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlinx.coroutines.CancellationException
/**
* Calls the specified function [block] with [this] value as its receiver and returns its
* encapsulated result if invocation was successful, catching any [Throwable] except
* [CancellationException] that was thrown from the [block] function execution and encapsulating it
* as a failure.
*/
public suspend inline fun <V> runSuspendCatching(block: () -> V): Result<V, Throwable> {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return try {
Ok(block())
} catch (e: Throwable) {
if (e is CancellationException) throw e
Err(e)
}
}
/**
* Calls the specified function [block] with [this] value as its receiver and returns its
* encapsulated result if invocation was successful, catching any [Throwable] except
* [CancellationException] that was thrown from the [block] function execution and encapsulating it
* as a failure.
*/
public suspend inline infix fun <T, V> T.runSuspendCatching(
block: T.() -> V
): Result<V, Throwable> {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return try {
Ok(block())
} catch (e: Throwable) {
if (e is CancellationException) throw e
Err(e)
}
}

View file

@ -10,6 +10,7 @@ plugins {
dependencies { dependencies {
api(projects.cryptoCommon) api(projects.cryptoCommon)
implementation(projects.coroutineUtils)
implementation(libs.androidx.annotation) implementation(libs.androidx.annotation)
implementation(libs.dagger.hilt.core) implementation(libs.dagger.hilt.core)
implementation(libs.kotlin.coroutines.core) implementation(libs.kotlin.coroutines.core)

View file

@ -8,9 +8,9 @@ package dev.msfjarvis.aps.crypto
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import com.github.michaelbull.result.runCatching
import dev.msfjarvis.aps.crypto.KeyUtils.tryGetId import dev.msfjarvis.aps.crypto.KeyUtils.tryGetId
import dev.msfjarvis.aps.crypto.KeyUtils.tryParseKeyring import dev.msfjarvis.aps.crypto.KeyUtils.tryParseKeyring
import dev.msfjarvis.aps.util.coroutines.runSuspendCatching
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@ -28,7 +28,7 @@ constructor(
override suspend fun addKey(key: PGPKey, replace: Boolean): Result<PGPKey, Throwable> = override suspend fun addKey(key: PGPKey, replace: Boolean): Result<PGPKey, Throwable> =
withContext(dispatcher) { withContext(dispatcher) {
runCatching { runSuspendCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
if (tryParseKeyring(key) == null) throw KeyManagerException.InvalidKeyException if (tryParseKeyring(key) == null) throw KeyManagerException.InvalidKeyException
val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION")
@ -49,7 +49,7 @@ constructor(
override suspend fun removeKey(key: PGPKey): Result<PGPKey, Throwable> = override suspend fun removeKey(key: PGPKey): Result<PGPKey, Throwable> =
withContext(dispatcher) { withContext(dispatcher) {
runCatching { runSuspendCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
if (tryParseKeyring(key) == null) throw KeyManagerException.InvalidKeyException if (tryParseKeyring(key) == null) throw KeyManagerException.InvalidKeyException
val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION")
@ -63,7 +63,7 @@ constructor(
override suspend fun getKeyById(id: GpgIdentifier): Result<PGPKey, Throwable> = override suspend fun getKeyById(id: GpgIdentifier): Result<PGPKey, Throwable> =
withContext(dispatcher) { withContext(dispatcher) {
runCatching { runSuspendCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
val keyFiles = keyDir.listFiles() val keyFiles = keyDir.listFiles()
if (keyFiles.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException if (keyFiles.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException
@ -89,7 +89,7 @@ constructor(
} }
if (matchResult != null) { if (matchResult != null) {
return@runCatching matchResult return@runSuspendCatching matchResult
} }
throw KeyManagerException.KeyNotFoundException("$id") throw KeyManagerException.KeyNotFoundException("$id")
@ -98,10 +98,10 @@ constructor(
override suspend fun getAllKeys(): Result<List<PGPKey>, Throwable> = override suspend fun getAllKeys(): Result<List<PGPKey>, Throwable> =
withContext(dispatcher) { withContext(dispatcher) {
runCatching { runSuspendCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
val keyFiles = keyDir.listFiles() val keyFiles = keyDir.listFiles()
if (keyFiles.isNullOrEmpty()) return@runCatching emptyList() if (keyFiles.isNullOrEmpty()) return@runSuspendCatching emptyList()
keyFiles.map { keyFile -> PGPKey(keyFile.readBytes()) }.toList() keyFiles.map { keyFile -> PGPKey(keyFile.readBytes()) }.toList()
} }
} }