{"record":{"id":"59498c1a22b0600c","repo":"microg/GmsCore","slug":"task-is-already-canceled","errorCode":null,"errorMessage":"Task is already canceled","messagePattern":"Task is already canceled","errorType":"exception","errorClass":"CancellationException","httpStatus":null,"severity":"error","filePath":"play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java","lineNumber":84,"sourceCode":"     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete\n     */\n    public static <TResult> TResult await(Task<TResult> task) throws ExecutionException, InterruptedException {\n        if (Looper.getMainLooper().getThread() == Thread.currentThread())\n            throw new IllegalStateException(\"Must not be invoked on main thread\");\n        if (task == null) throw new IllegalArgumentException(\"Task must not be null\");\n        if (task.isComplete()) return handleCompletedTask(task);\n        CountDownLatch latch = new CountDownLatch(1);\n        task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());\n        latch.await();\n        return handleCompletedTask(task);\n    }\n\n    private static <TResult> TResult handleCompletedTask(Task<TResult> task) throws ExecutionException {\n        if (task.isSuccessful()) {\n            return task.getResult();\n        }\n        if (task.isCanceled()) {\n            throw new CancellationException(\"Task is already canceled\");\n        }\n        throw new ExecutionException(task.getException());\n    }\n\n    /**\n     * Returns a {@link Task} that will be completed with the result of the specified {@code Callable}.\n     * <p/>\n     * If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a\n     * {@link RuntimeException} whose cause is the original throwable.\n     * <p/>\n     * The {@code Callable} will be called on the main application thread.\n     *\n     * @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor.\n     */\n    @Deprecated\n    public static <TResult> Task<TResult> call(Callable<TResult> callable) {\n        return call(TaskExecutors.MAIN_THREAD, callable);\n    }","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check task.isCanceled() before calling await/getResult and handle the cancellation path explicitly","Catch CancellationException separately from ExecutionException and treat it as a normal control-flow event (retry or abort)","If cancellation is unexpected, audit the CancellationToken and all trySetCancelled/cancel call sites to find who cancelled the task"],"exampleFix":"// before\ntry {\n    Result r = Tasks.await(task);\n} catch (ExecutionException e) {\n    // only handles failure\n}\n// after\ntry {\n    if (task.isCanceled()) {\n        // handle cancellation without throwing\n        return null;\n    }\n    Result r = Tasks.await(task);\n} catch (CancellationException e) {\n    // task was cancelled - retry or propagate\n} catch (ExecutionException e) {\n    // actual failure\n}","handlingStrategy":"try-catch","validationCode":"if (task.isComplete() && task.isCanceled()) {\n    // handle cancellation before awaiting\n    return null; // or retry\n}","typeGuard":"static <T> boolean isAwaitable(Task<T> t) {\n    return t != null && !(t.isComplete() && t.isCanceled());\n}","tryCatchPattern":"try {\n    T result = Tasks.await(task);\n} catch (CancellationException e) {\n    // cancelled: retry or abort gracefully\n} catch (ExecutionException e) {\n    // actual failure: inspect e.getCause()\n} catch (InterruptedException e) {\n    Thread.currentThread().interrupt();\n}","preventionTips":["Always catch CancellationException separately from ExecutionException","Audit CancellationToken and trySetCancelled call sites when cancellation surprises you","Remember Tasks.withTimeout cancels its wrapper task on expiry - await() will surface this error","Treat cancellation as control flow, not as a crash"],"tags":["android","cancellation","tasks","async"],"backgroundTag":"task-already-canceled","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}