Files
vscode/extensions/copilot/src/shared-fetch-utils/common
9bbf5e43c8 Coalesce content exclusion fetches to stop exhausting the GitHub API rate limit (#328268)
* Coalesce content exclusion fetches to stop exhausting the GitHub API rate limit

Every caller that discovered a new repository triggered a refresh of the
content exclusion rules for *every* known repository, so request volume grew
quadratically with repository count. In a workspace with many git repos (an
AOSP checkout, in the reported case) this produced ~16k requests to
api.github.com in 30 minutes, exhausting the account's 5k/hour REST budget and
starving everything sharing it, including Copilot token refresh.

The endpoint is https://api.github.com/copilot_internal/content_exclusion, so
it draws on the user's ordinary REST quota rather than a CAPI budget.

- Coalesce per repository using shared DeferredPromises, a short batching
  window and a bounded-concurrency Limiter, so each repo is fetched at most
  once per TTL and each caller only waits on the repos it asked for. A
  regression test measures 75 requests -> 3 for 26 repositories.
- Only cache rules on a successful response. Empty placeholder rules were
  written on discovery and left in place on failure, making a failed fetch
  indistinguishable from "this repo has no exclusions" and preventing a retry
  for 30 minutes, so exclusions silently stopped applying while rate limited.
- Only memoise a negative verdict once the relevant rules actually loaded,
  which otherwise left files checked during an outage permanently allowed.
- Add a shared rateLimitBackoffMiddleware covering 429 and quota-exhausted 403
  responses, honouring Retry-After and x-ratelimit-reset, and move both
  RemoteContentExclusion and CloudSessionApiClient onto it. This replaces a
  third hand-rolled copy of the same backoff logic.
- Precompile glob patterns, track the regex rule count directly, and only
  invalidate memoised results when rules that could change an outcome arrive.

Fixes #322275

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32f94728-158b-4639-b789-a5dd483f043f

* Address review feedback on cache invalidation and request lifecycle

Five correctness issues raised in review, each with a test that fails against
the previous implementation.

- rateLimitBackoffMiddleware: a response that was already in flight could clear
  a block established by a concurrent rate-limited request, letting later calls
  reach the server during the window the server asked us to wait out. The
  backoff is now only reset once the active block has elapsed.
- isIgnored returned a memoised verdict before reaching the staleness check, so
  a URI that had been evaluated once never triggered a refresh and could miss
  newly added exclusions indefinitely. Verdicts are now tagged with a rule
  generation and are only trusted while the rules behind them are unchanged and
  unexpired.
- applyRules only invalidated verdicts when the incoming rules were non-empty,
  so a refresh that removed the last rule left files excluded permanently.
  Incoming rules are now compared against the previous set, which also avoids
  invalidating on an unchanged refresh.
- drainPendingRepos cleared the pending map before its batches completed, so a
  lookup arriving while a request was slow queued a duplicate fetch every
  batching window. Entries now stay registered until their request settles, and
  are removed only if still owned by that attempt.
- dispose only settled repos still queued. Limiter.dispose drops queued
  factories without running them, so with more than five batches the callers
  awaiting them never resolved. Pending entries are now settled before the
  limiter is disposed, and enqueues after disposal resolve immediately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32f94728-158b-4639-b789-a5dd483f043f

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32f94728-158b-4639-b789-a5dd483f043f
2026-07-31 08:37:52 -04:00
..