Skip to content

fix(android): prevent ANR from concurrent token refreshes - #2516

Open
sentry[bot] wants to merge 1 commit into
masterfrom
seer/fix/android-anr-token-refresh
Open

sentry[bot] wants to merge 1 commit into
masterfrom
seer/fix/android-anr-token-refresh

Conversation

@sentry

@sentry sentry Bot commented Sep 13, 2026

Copy link
Copy Markdown

This PR addresses an Application Not Responding (ANR) issue on Android devices, where the main thread was observed to block in JNI calls during vsync dispatch.

Root Cause:
The ANR was traced to a burst of JavaScript activity, specifically when multiple API calls simultaneously encountered a 401 (Unauthorized) error. This led to:

  1. Concurrent Token Refreshes: The useActivitySSE hook had its own token refresh logic, separate from the shared withRefreshToken utility. This meant that an SSE 401 and other API 401s could trigger two independent token refresh HTTP requests.
  2. Duplicate Token Saves: When withRefreshToken's refreshTokenPromise resolved, every concurrent caller waiting on it would independently call saveNewTokens(). This resulted in N redundant Zustand store updates and N React re-render schedules occurring simultaneously.
  3. Microtask Avalanche: The combined effect of multiple token refreshes and duplicate store updates created a large fan-out of JavaScript promise continuations, leading to a significant spike in JS heap allocation and triggering ART runtime thread checkpoints or GC stop-the-world pauses. If these pauses exceeded 5 seconds, the Android main thread, which was waiting on JNI calls for UI rendering, would be flagged as unresponsive, causing an ANR.

Solution:
This fix implements three key changes to mitigate the JS microtask burst:

  1. Consolidated Token Refresh Logic: Introduced ensureTokenRefreshed() in lib/utils/utils.ts to centralize the token refresh mechanism. This function now manages the refreshTokenPromise and ensures that only one HTTP token refresh request is ever in flight, even if multiple parts of the app simultaneously encounter 401s.
  2. Single Token Save: The saveNewTokens() call has been moved inside the ensureTokenRefreshed() promise chain. This guarantees that tokens are saved to the store exactly once after a successful refresh, eliminating redundant Zustand updates.
  3. Staggered API Retries: In withRefreshToken, non-originating callers (those that didn't initiate the token refresh) now yield for one event-loop tick (await new Promise(r => setTimeout(r, 0))) before retrying their original API call. This spreads the fan-out of concurrent retries across multiple event-loop ticks, preventing a single, large microtask avalanche that could trigger GC pressure and ANRs.
  4. SSE Integration: The useActivitySSE hook (hooks/useActivitySSE.ts) has been updated to use the new ensureTokenRefreshed() utility, routing its token refresh needs through the shared mechanism.

Fixes SOLID-TS

@vercel

vercel Bot commented Sep 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
solid-app Ignored Ignored Preview Sep 13, 2026 2:04pm UTC
solid-app-staging Ignored Ignored Preview Sep 13, 2026 2:04pm UTC

Request Review

@MusabShakeel576

Copy link
Copy Markdown
Contributor

@sentry review

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.

1 participant