microg/GmsCore · warning · CancellationException
Task $this was cancelled normally.
Error message
Task $this was cancelled normally.
What it means
play-services-tasks Kotlin await() throws this CancellationException when the underlying GMS Task completes in a cancelled state (isCanceled == true, no exception, no result). await() suspends on the Task's completion; if the task was cancelled (e.g. via a CancellationToken or cancellation propagated by the producer), there is no result to return, so the await itself cancels cooperatively with this message.
Source
Thrown at play-services-tasks/ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt:124
*
* This suspending function is cancellable and cancellation is bi-directional:
* * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* cancels the [cancellationTokenSource] and throws a [CancellationException].
* * If the task is cancelled, then this function will throw a [CancellationException].
*
* Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
* leads to an unspecified behaviour.
*/
@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
suspend fun <T> Task<T>.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource)
private suspend fun <T> Task<T>.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
// fast path
if (isComplete) {
val e = exception
return if (e == null) {
if (isCanceled) {
throw CancellationException("Task $this was cancelled normally.")
} else {
@Suppress("UNCHECKED_CAST")
result as T
}
} else {
throw e
}
}
return suspendCancellableCoroutine { cont ->
addOnCompleteListener {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
if (it.isCanceled) cont.cancel() else cont.resume(it.result as T)
} else {
cont.resumeWithException(e)
}View on GitHub (pinned to 157c9d86ac)
Solutions
- Check `task.isCanceled` / attach `addOnCanceledListener` before awaiting, and handle cancellation explicitly instead of expecting a result.
- If the coroutine is being cancelled intentionally, wrap the await in a scope whose Job lifecycle matches the Task and treat CancellationException as normal control flow (rethrow, don't swallow).
- Use `Tasks.whenComplete`/`addOnCompleteListener` style callbacks if you need to observe cancelled tasks without coroutine cancellation.
- Ensure the producer isn't calling Task.cancel() due to an expired CancellationToken you created; keep the CancellationTokenSource alive as long as you intend to await.
Example fix
// before
val result = task.await()
// after
if (task.isComplete && task.isCanceled) {
// handle cancellation, e.g. return default or notify UI
return
}
val result = task.await() Defensive patterns
Strategy: try-catch
Validate before calling
if (task.isComplete && task.isCanceled) return // skip await
task.addOnCanceledListener { /* observe cancellation early */ } Type guard
fun <T> Task<T>.hasResult(): Boolean = isComplete && !isCanceled && exception == null
Try / catch
try {
val result = task.await()
} catch (e: CancellationException) {
throw e // cancellation is control flow: rethrow, don't swallow
} Prevention
- Check isCanceled before awaiting completed tasks
- Keep CancellationTokenSource alive as long as you await
- Never catch and ignore CancellationException
- Match coroutine Job scope to the Task lifecycle
When it happens
Trigger: Calling `task.await()` (from Tasks.kt) on a Task that completes with Task.cancel() or was created from a CancellationTokenSource that was cancelled before/during execution — the fast path `if (isComplete)` sees isCanceled true.
Common situations: Awaiting a location or sign-in Task whose Activity/Detachables were cancelled; cancelling a coroutine scope while the Task producer invokes Task.cancel(); passing an already-cancelled CancellationToken to a Google API; race between cancellation and await().
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Task is already canceled
- continueWithTask is not implemented
- onSuccessTask is not implemented
- Must not be invoked on main thread
- Task is canceled
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/8bd107434b5ab204.
Report an issue: GitHub.