fix: Single-flight token refresh coordination for concurrent requests #130
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #126 - Concurrent requests cause multiple token refresh events
This PR implements a zero-dependency single-flight token refresh mechanism that ensures only one refresh operation occurs when multiple requests fail simultaneously due to an expired/invalid access token. The fix is implemented in both
fresh_dioandfresh_graphqlpackages, with shared coordination logic in the corefreshpackage.Problem
When multiple HTTP requests receive 401 responses (or GraphQL UNAUTHENTICATED errors) simultaneously, each request independently triggers
refreshToken, resulting in multiple concurrent refresh operations instead of a single one:Solution
This PR implements a single-flight refresh pattern directly in the
FreshMixin:Key Design Decisions
synchronizedpackagefresh_dioonlyfresh_dioANDfresh_graphqlidentical()checkFreshinterceptorFreshMixin(shared)How It Works
singleFlightRefreshmethod inFreshMixin: A new method that coordinates concurrent refresh attemptsfinallyblock ensures_refreshFutureis cleared even on exceptionsWhy This Approach is Better
Futurefor coordinationQueuedInterceptor: Dio's interceptor serializes requests, so we compare tokens instead of just checking for in-flight refreshesfresh_graphqltoo: GraphQL streams run concurrently, and this solution handles both execution modelsRevokeTokenException, generic exceptions, and ensures no hangsChanges
Core
freshpackagesingleFlightRefresh(refreshAction, {tokenBeforeRefresh})method toFreshMixin_refreshFuturefield to track in-flight refresh operations_performRefreshhelper with proper try/catch/finally cleanupfresh_diopackageonRequestto store the token used for each request inoptions.extra['_fresh_request_token']_tryRefreshto usesingleFlightRefreshwithtokenBeforeRefreshparameterfresh_graphqlpackagerequest()to capturetokenUsedForRequestat stream startsingleFlightRefreshwithtokenBeforeRefreshfor coordinationTest Coverage
Added 5 comprehensive test scenarios for each package:
refreshTokencalled exactly onceTest Results
fresh: 15 tests ✅fresh_dio: 32 tests ✅ (5 new)fresh_graphql: 17 tests ✅ (5 new)Breaking Changes
None. The fix is backward compatible.
Checklist
fresh_dioandfresh_graphqlNote
The
pubspec.yamlfiles currently use path dependencies for local development. Before merging, these should be updated to proper version constraints.