Skip to content

fix: prevent token refresh thundering herd#333

Open
aaguiarz wants to merge 4 commits intomainfrom
fix/issue-332-concurrent-token-refresh
Open

fix: prevent token refresh thundering herd#333
aaguiarz wants to merge 4 commits intomainfrom
fix/issue-332-concurrent-token-refresh

Conversation

@aaguiarz
Copy link
Member

@aaguiarz aaguiarz commented Feb 16, 2026

Summary

Ensures concurrent token header reads share one in-flight refresh instead of issuing parallel token exchange requests.

Changes

  • Added regression test asserting 5 parallel calls only trigger 1 token request
  • Added an internal refreshAccessTokenPromise lock in Credentials
  • Reused the same refresh promise for concurrent callers and cleared it when settled

Potential Breaking Change

  • Concurrent expired-token requests now share a single refresh result.
  • Under transient token endpoint failures, all waiting callers may fail together (instead of independent parallel refresh attempts), which changes failure/retry dynamics.

Fixes #332

Summary by CodeRabbit

  • Improvements
    • Optimized concurrent authentication token requests to prevent duplicate requests and reduce server load.

Copilot AI review requested due to automatic review settings February 16, 2026 18:55
@aaguiarz aaguiarz requested a review from a team as a code owner February 16, 2026 18:55
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Implements deduplication of concurrent token refresh requests in Credentials by caching an in-flight promise. When multiple callers request access tokens simultaneously with no cached token available, they now await the same token refresh promise instead of triggering multiple simultaneous token exchanges. Includes test verification for concurrent request behavior.

Changes

Cohort / File(s) Summary
Token Request Deduplication
credentials/credentials.ts
Adds refreshAccessTokenPromise private member to cache in-flight client credentials token requests. In getAccessTokenValue, reuses the promise for concurrent callers requesting tokens without a valid cache, clearing it after completion.
Concurrent Request Tests
tests/credentials.test.ts
Adds test case verifying concurrent calls to getAccessTokenHeader trigger only a single token endpoint request, with all callers receiving the same token response.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: prevent token refresh thundering herd' clearly and concisely summarizes the main objective—preventing multiple concurrent token refresh requests (thundering herd problem).
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #332: adds an internal refreshAccessTokenPromise lock to deduplicate concurrent token refresh requests and includes regression tests verifying only one token request occurs.
Out of Scope Changes check ✅ Passed All changes are scoped to the token refresh deduplication fix: internal Credentials lock mechanism and regression tests; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-332-concurrent-token-refresh

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 and usage tips.

@dosubot
Copy link

dosubot bot commented Feb 16, 2026

Related Documentation

Checked 8 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@codecov-commenter
Copy link

codecov-commenter commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.31%. Comparing base (a7090e2) to head (5ab0260).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #333      +/-   ##
==========================================
+ Coverage   89.67%   90.31%   +0.63%     
==========================================
  Files          25       25              
  Lines        1492     1507      +15     
  Branches      279      263      -16     
==========================================
+ Hits         1338     1361      +23     
+ Misses         94       88       -6     
+ Partials       60       58       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #332 by deduplicating concurrent access token refreshes so multiple callers share a single in-flight token exchange instead of triggering parallel requests.

Changes:

  • Added an in-flight refresh lock (refreshAccessTokenPromise) in Credentials to dedupe concurrent refresh calls.
  • Added a regression test asserting 5 concurrent getAccessTokenHeader() calls only trigger 1 token request.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
tests/credentials.test.ts Adds concurrency regression test for single token exchange under parallel access-token header reads.
credentials/credentials.ts Introduces a shared in-flight refresh promise to prevent concurrent token refresh thundering herd.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 45 to 49
export class Credentials {
private accessToken?: string;
private accessTokenExpiryDate?: Date;
private refreshAccessTokenPromise?: Promise<string | undefined>;

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

This file is marked as autogenerated (header) and CONTRIBUTING.md notes autogenerated files should not be modified directly; changes should be made in the sdk-generator templates/in tandem to avoid the fix being overwritten on regen. Please ensure this concurrency-lock change is also applied in the generator source (or confirm this file is intentionally maintained manually despite the header).

Copilot uses AI. Check for mistakes.
Comment on lines 547 to 557
nock(expectedBaseUrl)
.post(expectedPath)
.times(5)
.delay(20)
.reply(() => {
tokenRequestCount += 1;
return [200, {
access_token: "shared-token",
expires_in: 300,
}];
});
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The test name asserts a single token request, but the nock interceptor is configured with .times(5) and the scope isn't asserted with scope.isDone(). Consider making the mock expect exactly one call (e.g., .once()/default) and asserting isDone() so the test intent is enforced by nock as well as by tokenRequestCount.

Copilot uses AI. Check for mistakes.
@aaguiarz
Copy link
Member Author

Addressed the actionable test-review comment in commit d31b421.

Updated the concurrency regression test to enforce intent directly with nock:

  • switched interceptor from .times(5) to .once()
  • captured the scope and added expect(scope.isDone()).toBe(true)

This now verifies both:

  • all 5 callers receive the shared token
  • exactly one token endpoint request was made

Validated locally with:

  • npm test -- tests/credentials.test.ts -t "single token request for concurrent"

On the autogenerated-file note: agreed in principle; this PR remains scoped to the runtime fix and test behavior.

@aaguiarz
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 545 to 549
let tokenRequestCount = 0;

const scope = nock(expectedBaseUrl)
.post(expectedPath)
.once()
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

tokenRequestCount is redundant with nock(...).post(...).once()/scope.isDone() (a second token request would already fail the test). Consider removing the counter to simplify the test and avoid incidental state.

Copilot uses AI. Check for mistakes.
Comment on lines +145 to 152
if (!this.refreshAccessTokenPromise) {
this.refreshAccessTokenPromise = this.refreshAccessToken().finally(() => {
this.refreshAccessTokenPromise = undefined;
});
}

return this.refreshAccessTokenPromise;
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

The new in-flight refresh lock is cleared via .finally(...), but there’s no regression test covering the failure path (e.g., token endpoint returns 500) to ensure: (1) all concurrent callers see the same rejection, and (2) a subsequent call retries and triggers a new token request after the promise is cleared. Adding a test for this would help prevent future regressions where the lock could get stuck on a rejected promise.

Copilot uses AI. Check for mistakes.
@aaguiarz
Copy link
Member Author

Addressed the actionable review feedback from #333 (review) in commit 5ab0260.

What changed:

  • Removed the redundant tokenRequestCount assertion from the concurrent token-read test, since nock(...).once() + scope.isDone() already enforces single request behavior.
  • Added a new regression test (should clear shared refresh promise after failure and retry on the next call) that verifies:
    • concurrent callers share the same rejection during a failed token refresh, and
    • a subsequent call retries successfully (proving the in-flight refresh promise is cleared after rejection).

Validation run:

  • npm test -- tests/credentials.test.ts

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

fix: deduplicate concurrent token refresh requests in Credentials

2 participants