microg/GmsCore · error · CancellationException

Task is already canceled

Error message

Task is already canceled

What it means

Tasks.await() calls handleCompletedTask() once the task has completed. If the completed Task was cancelled rather than successful or failed, the method throws a CancellationException with this message, since there is no result to return and no cause exception to wrap. It distinguishes an intentionally cancelled Task from a failed one.

Source

Thrown at play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java:84

     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
     */
    public static <TResult> TResult await(Task<TResult> task) throws ExecutionException, InterruptedException {
        if (Looper.getMainLooper().getThread() == Thread.currentThread())
            throw new IllegalStateException("Must not be invoked on main thread");
        if (task == null) throw new IllegalArgumentException("Task must not be null");
        if (task.isComplete()) return handleCompletedTask(task);
        CountDownLatch latch = new CountDownLatch(1);
        task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
        latch.await();
        return handleCompletedTask(task);
    }

    private static <TResult> TResult handleCompletedTask(Task<TResult> task) throws ExecutionException {
        if (task.isSuccessful()) {
            return task.getResult();
        }
        if (task.isCanceled()) {
            throw new CancellationException("Task is already canceled");
        }
        throw new ExecutionException(task.getException());
    }

    /**
     * Returns a {@link Task} that will be completed with the result of the specified {@code Callable}.
     * <p/>
     * If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a
     * {@link RuntimeException} whose cause is the original throwable.
     * <p/>
     * The {@code Callable} will be called on the main application thread.
     *
     * @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor.
     */
    @Deprecated
    public static <TResult> Task<TResult> call(Callable<TResult> callable) {
        return call(TaskExecutors.MAIN_THREAD, callable);
    }

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Check task.isCanceled() before calling await/getResult and handle the cancellation path explicitly
  2. Catch CancellationException separately from ExecutionException and treat it as a normal control-flow event (retry or abort)
  3. If cancellation is unexpected, audit the CancellationToken and all trySetCancelled/cancel call sites to find who cancelled the task

Example fix

// before
try {
    Result r = Tasks.await(task);
} catch (ExecutionException e) {
    // only handles failure
}
// after
try {
    if (task.isCanceled()) {
        // handle cancellation without throwing
        return null;
    }
    Result r = Tasks.await(task);
} catch (CancellationException e) {
    // task was cancelled - retry or propagate
} catch (ExecutionException e) {
    // actual failure
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (task.isComplete() && task.isCanceled()) {
    // handle cancellation before awaiting
    return null; // or retry
}

Type guard

static <T> boolean isAwaitable(Task<T> t) {
    return t != null && !(t.isComplete() && t.isCanceled());
}

Try / catch

try {
    T result = Tasks.await(task);
} catch (CancellationException e) {
    // cancelled: retry or abort gracefully
} catch (ExecutionException e) {
    // actual failure: inspect e.getCause()
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

Prevention

When it happens

Trigger: Awaiting a Task whose TaskCompletionSource.trySetCancelled()/cancel() was called, or a Task cancelled by a CancellationToken (e.g. by Tasks.withTimeout when its timeout fires, or by an operation cancelled on logout/navigation), then calling getResult()/await() on it.

Common situations: A withTimeout wrapper fires while awaiting and the resulting task is cancelled; user navigates away and the app cancels pending Firestore/Firebase work; a CancellationToken passed to TaskCompletionSource is cancelled before completion; retry logic that cancels superseded requests.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/59498c1a22b0600c. Report an issue: GitHub.