Skip to content

fix: guard against fatal double-resume crash in CredentialsManager#1031

Merged
pmathew92 merged 2 commits into
mainfrom
sdk_crash
Jul 23, 2026
Merged

fix: guard against fatal double-resume crash in CredentialsManager#1031
pmathew92 merged 2 commits into
mainfrom
sdk_crash

Conversation

@pmathew92

@pmathew92 pmathew92 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Why

Customers report a fatal, uncaught IllegalStateException: "Already resumed" originating in the credentials managers

Root cause

An executor block can invoke a terminal callback (onSuccess) and then have a Throwable escape after it — e.g. in continueGetCredentials, callback.onSuccess(...) fires, and the refresh-branch inner catch only handles AuthenticationException, so any other Throwable propagates up to runCatchingOnExecutor, which then calls callback.onFailure(...). That is two terminal callbacks for one request.

Through the suspendCancellableCoroutine bridges (awaitCredentials/awaitSsoCredentials/awaitApiCredentials), the first callback resumes the continuation and the second resumes it again. A second resume throws "Already resumed" on the single-threaded serial executor, where nothing catches it → process death.

Fix (defense in depth)

Layer A — root cause (BaseCredentialsManager)
runCatchingOnExecutor now wraps the callback in a new SingleShotCallback (backed by AtomicBoolean), so any terminal callback after the first is silently dropped — upholding the fire-once contract of Callback even when a block both invokes the callback and later throws. Fatal errors (VirtualMachineError/ThreadDeath/LinkageError) are still rethrown. All call sites shadow the block's callback parameter so existing inner callback.xxx calls route through the guard with no body edits.

Layer B — bridge safety net (CredentialsManager, SecureCredentialsManager)
Every coroutine bridge now guards its resume with if (continuation.isActive), so even a double callback reaching a bridge directly can never resume the continuation twice.

Tests

Added regression tests to both CredentialsManagerTest and SecureCredentialsManagerTest:

  • shouldNotInvokeCallbackTwiceWhenConsumerThrowsAfterSuccessOnGetCredentials — covers Layer A via the callback API.
  • shouldNotResumeContinuationTwiceWhenCallbackFiresTwiceOnAwaitCredentials — covers Layer B via the coroutine/await API.

Each test was verified to fail with the respective guard removed (reproducing IllegalStateException: Already resumed) and pass with the fix.

@pmathew92
pmathew92 requested a review from a team as a code owner July 23, 2026 08:35
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Credential manager executor callbacks now deliver at most one terminal result, while suspend credential APIs avoid resuming cancelled continuations. Manager integrations and regression tests cover callback exceptions, duplicate callbacks, and duplicate coroutine resumption.

Changes

Credential completion safety

Layer / File(s) Summary
Single-shot executor callbacks
auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt
runCatchingOnExecutor accepts an explicit callback and suppresses subsequent terminal results, including failures thrown after success.
Manager callback and cancellation wiring
auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt, auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt
Credential flows pass explicit callbacks into executor work and check continuation activity before resuming.
Completion regression coverage
auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt, auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt
Tests cover post-success consumer exceptions and callbacks that invoke both success and failure. User asked "Generate a walkthrough of the following pull request."

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant CredentialsManager
  participant BaseCredentialsManager
  participant Executor
  participant Continuation
  Caller->>CredentialsManager: request credentials
  CredentialsManager->>BaseCredentialsManager: run executor operation
  BaseCredentialsManager->>Executor: execute operation
  Executor->>CredentialsManager: success or failure callback
  CredentialsManager->>Continuation: resume if active
  Executor-->>BaseCredentialsManager: optional duplicate callback or exception
  BaseCredentialsManager-->>CredentialsManager: suppress duplicate terminal result
Loading

Possibly related PRs

  • auth0/Auth0.Android#997: Both changes update the shared executor wrapper and credential manager flows; this PR adds single-shot callback behavior and related coroutine tests.

Suggested reviewers: utkrishtsahu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main fix: preventing fatal double-resume/callback crashes in the credentials managers.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sdk_crash

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt (2)

429-445: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Broad catch (t: Throwable) — intentional bulkhead, but verify scope and log-tag consistency.

Catching generic Throwable here runs counter to the general guideline of using specific exception types, though it's plausibly required as a last-resort bulkhead so any unexpected error from consumer/bridge code still yields a single terminal callback instead of crashing the executor thread. Two small follow-ups:

  • Consider a one-line comment explaining why the broad catch is intentional here (helps future maintainers avoid narrowing it by mistake).
  • The hardcoded "BaseCredentialsManager" log tag diverges from the this::class.java.simpleName pattern used elsewhere in this same file (e.g. saveDPoPThumbprint, validateDPoPState).

As per coding guidelines, "Use specific exception types and typed status/error codes; do not catch generic exceptions or rely on error-message string matching."

♻️ Optional tweak for log tag consistency
-            Log.e("BaseCredentialsManager", "Unexpected error in executor block", t)
+            Log.e(this::class.java.simpleName, "Unexpected error in executor block", t)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt`
around lines 429 - 445, Add a concise comment above the broad catch in
runCatchingOnExecutor explaining that it intentionally acts as a last-resort
bulkhead for unexpected callback or bridge failures while preserving a single
terminal callback. Also replace the hardcoded "BaseCredentialsManager" Log.e tag
with the class-name pattern already used by nearby methods, without changing the
existing fatal-error filtering or callback behavior.

Source: Coding guidelines


447-468: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log dropped duplicate terminal callback calls.

SingleShotCallback already guards multiple callbacks, but when a second explicit onSuccess or onFailure is dropped there’s no signal. Add a short log entry on each dropped branch to catch future regressions where an executor calls both terminal callbacks without throwing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt`
around lines 447 - 468, Update SingleShotCallback’s onSuccess and onFailure
methods to log a brief entry whenever handled.compareAndSet rejects a duplicate
terminal callback, while preserving the existing delegate invocation only for
the first callback.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt`:
- Around line 429-445: Add a concise comment above the broad catch in
runCatchingOnExecutor explaining that it intentionally acts as a last-resort
bulkhead for unexpected callback or bridge failures while preserving a single
terminal callback. Also replace the hardcoded "BaseCredentialsManager" Log.e tag
with the class-name pattern already used by nearby methods, without changing the
existing fatal-error filtering or callback behavior.
- Around line 447-468: Update SingleShotCallback’s onSuccess and onFailure
methods to log a brief entry whenever handled.compareAndSet rejects a duplicate
terminal callback, while preserving the existing delegate invocation only for
the first callback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 981de0dc-1e38-4b49-8bb3-5fe6212fbd64

📥 Commits

Reviewing files that changed from the base of the PR and between 8850f7f and ddf742e.

📒 Files selected for processing (5)
  • auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt

@utkrishtsahu utkrishtsahu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM,no remarks.

@pmathew92
pmathew92 merged commit 939f2e5 into main Jul 23, 2026
7 checks passed
@pmathew92
pmathew92 deleted the sdk_crash branch July 23, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants