Visitar URL original
fix(interceptors): trim trailing ejected handlers by jasonsaayman · Pull Request #11087 · axios/axios · GitHub
Skip to content

fix(interceptors): trim trailing ejected handlers - #11087

Merged
jasonsaayman merged 8 commits into
v1.xfrom
fix/interceptor-handler-compaction
Jul 29, 2026
Merged

jasonsaayman merged 8 commits into
v1.xfrom
fix/interceptor-handler-compaction

Conversation

@jasonsaayman

@jasonsaayman jasonsaayman commented Jul 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes unbounded sparse-array growth in InterceptorManager caused by ejected interceptors leaving trailing null tombstones.

The implementation preserves the existing handlers array and public types. Trailing tombstones are removed after ejection, with compaction deferred during iteration so newly registered interceptors are not accidentally included in the current interceptor chain.

Linked issue

Closes #11070

Changes

  • Trim trailing null handlers after interceptor ejection.
  • Defer compaction while forEach() is iterating to preserve existing snapshot behavior.
  • Keep handlers as an array and leave ESM/CommonJS declarations unchanged.
  • Add focused tests covering repeated registration/ejection, interior tombstones, mutation during iteration, and clear() behavior.
  • Add the fix to PRE_RELEASE_CHANGELOG.md.

Testing

Ran suit locally

Checklist

  • Tests added or updated (or N/A with reason)
  • Docs/types updated if public API changed (N/A — public API and types are unchanged)
  • No breaking changes (or called out explicitly above)

Summary by cubic

Prevents InterceptorManager handler array growth by trimming trailing ejected handlers and using monotonic, non-reused IDs. Also clears stale handler entries when the handlers array is replaced or reset in place; iteration behavior and public types stay the same, and compaction runs after iteration.

Description

  • Trim trailing null entries after eject(); keep interior tombstones.
  • Use monotonic IDs from use(); ignore stale IDs after clear(), in-place reset, or array replacement.
  • Clear stale ID-to-handler entries when handlers is replaced or emptied in place; resync on clear().
  • Defer trimming during forEach(); compact once iteration ends.
  • Preserve handlers as an array and keep ESM/CJS types unchanged.
  • Closes InterceptorManager sparse array growth #11070.

Docs

  • Update /docs/ (interceptors):
    • Trailing tombstones are trimmed after ejection.
    • IDs from use() are monotonic and are not reused; stale IDs are ignored after clear(), in-place reset, or array replacement.
    • Iteration visits only handlers present when it starts.
    • Recommend .clear() over replacing the array or changing its length.

Testing

  • Added tests/unit/core/InterceptorManager.test.js:
    • No growth under repeated register/eject cycles.
    • No ID reuse; stale IDs are ignored after clear()/replacement/in-place reset.
    • Deferred compaction during iteration; compaction after.
    • ID invalidation when handlers is replaced; clear() during iteration; in-place resets do not leak entries.
  • Updated browser tests to call axios.interceptors.request.clear() and .response.clear().

Semantic version impact

Patch: bug fix with backward-compatible behavior.

Written for commit a4cc018. Summary will update on new commits.

Review in cubic

@jasonsaayman jasonsaayman self-assigned this Jul 19, 2026
@jasonsaayman jasonsaayman added the commit::fix The PR is related to a bugfix label Jul 19, 2026

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files

Confidence score: 5/5

  • Safe to merge after the addressed issues were fixed.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread lib/core/InterceptorManager.js
Copilot AI review requested due to automatic review settings July 21, 2026 15:50

Copilot AI 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.

Pull request overview

This PR fixes unbounded growth of InterceptorManager.handlers by trimming trailing null tombstones on eject(), while preserving iteration snapshot semantics by deferring trimming until forEach() completes. It also introduces monotonic interceptor IDs so trimmed slots aren’t reused and stale IDs don’t accidentally target newly registered interceptors. 🏄

Changes:

  • Trim trailing null handler entries after eject() (immediately when not iterating; deferred until iteration ends).
  • Switch use() to return monotonic IDs tracked via an internal Map, rather than array indexes.
  • Add unit tests covering trimming behavior, non-reuse of IDs, and mutation (eject(), use(), clear()) during forEach(); add a PRE_RELEASE changelog entry.

Reviewed changes

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

File Description
lib/core/InterceptorManager.js Adds internal ID tracking + deferred trimming to prevent sparse-array growth while preserving iteration semantics.
tests/unit/core/InterceptorManager.test.js New focused unit coverage for trimming, monotonic IDs, and mutation during iteration.
PRE_RELEASE_CHANGELOG.md Notes the interceptor storage fix in the unreleased changelog.

Comment thread lib/core/InterceptorManager.js
Comment thread lib/core/InterceptorManager.js
Comment thread lib/core/InterceptorManager.js
Comment thread PRE_RELEASE_CHANGELOG.md Outdated
Comment thread PRE_RELEASE_CHANGELOG.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 21, 2026 16:03

Copilot AI 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.

Pull request overview

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

Comments suppressed due to low confidence (1)

lib/core/InterceptorManager.js:46

  • Potential memory leak / regression for code that resets interceptors by reassigning handlers (this repo does this in browser tests). With the new handlerEntries Map holding strong references to handler objects, axios.interceptors.*.handlers = [] detaches the array but leaves prior handlers reachable via handlerEntries, reintroducing unbounded growth (now in the Map) and preventing GC. 🏄

Consider defensively detecting handlers array replacement and clearing handlerEntries (e.g., track the last handlers reference in internals and clear() the map when it changes) or otherwise avoid storing strong handler references in the Map so external handlers reassignment doesn’t retain them.

    this.handlers.push(handler);
    internals.handlerEntries.set(id, {
      handler,
      index: this.handlers.length - 1,
    });

Copilot AI review requested due to automatic review settings July 21, 2026 18:56

Copilot AI 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.

Pull request overview

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

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread lib/core/InterceptorManager.js Outdated
Copilot AI review requested due to automatic review settings July 29, 2026 17:30

Copilot AI 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.

Pull request overview

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

Comments suppressed due to low confidence (2)

PRE_RELEASE_CHANGELOG.md:7

  • 🏄 PRE_RELEASE_CHANGELOG.md headings look mis-nested: ## Bug Fixes currently sits at the same level as ## Unreleased, while PRE_RELEASE_DOCS.md uses ## Unreleased with ### ... subsections. Also the bullet text “interceptor ID identity across registrations” is ambiguous now that IDs are monotonic and can diverge from handler indices; consider clarifying that IDs are monotonic/not reused and handlers remain an array. (You may also want to add a matching entry to PRE_RELEASE_DOCS.md to track the interceptor docs update at release time.)
## Unreleased

## Bug Fixes

- **Interceptor storage:** Removed trailing interceptor tombstones after ejection so repeated register-then-eject cycles no longer grow the handlers array, while preserving its public array shape, interceptor iteration behavior, and interceptor ID identity across registrations. (**#11070**)

tests/unit/core/InterceptorManager.test.js:92

  • 🏄 This test reaches into the InterceptorManager internals via Object.getOwnPropertySymbols(manager)[0], which relies on symbol enumeration order. It’s more robust to locate the symbol by description so the test won’t break if additional symbol-keyed fields are added later.
    const internals = manager[Object.getOwnPropertySymbols(manager)[0]];

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Greptile Summary

Fixes sparse interceptor storage growth while preserving iteration semantics.

  • Assigns monotonic interceptor IDs and tracks their handler entries separately from array indexes.
  • Trims trailing ejected handlers immediately or after active iterations finish.
  • Invalidates stale IDs when handler storage is cleared or replaced.
  • Updates browser cleanup and adds focused lifecycle and mutation tests.
  • Documents the fix in the pre-release changelog.

Confidence Score: 5/5

The PR appears safe to merge with no concrete correctness or security failures identified.

The new bookkeeping preserves opaque interceptor IDs, validates stale mappings before ejection, defers compaction across nested iteration, and restores state through a finally block.

T-Rex T-Rex Logs

What T-Rex did

  • The focused test suite was executed and exited with status code 0 in 251 ms.
  • During the run, the harness initially exited with status 1 because handlerEntries.size was 3 instead of 1.
  • After repeated ejection, the harness later exited with handlersLength 1, visitation progressing from ['persistent'] to ['persistent','added'], deferred compaction to length 1, and exactly one retained entry after in-place clear.
  • Artifacts were examined to support the above observations, including three log artifacts and one JavaScript artifact documenting the state and behavior.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
lib/core/InterceptorManager.js Introduces monotonic IDs, stale-entry synchronization, and iteration-safe trailing tombstone trimming without exposing a concrete behavioral defect.
tests/unit/core/InterceptorManager.test.js Covers repeated registration and ejection, stale IDs, direct storage reset, deferred compaction, and clear-during-iteration behavior.
tests/browser/adapter.browser.test.js Replaces direct interceptor-array assignment with the supported clear operation during cleanup.
tests/browser/interceptors.browser.test.js Uses the manager clear API for browser-test isolation.
PRE_RELEASE_CHANGELOG.md Records the interceptor tombstone-growth fix and preserved behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  U[use interceptor] --> I[Assign monotonic ID]
  I --> A[Append handler and map ID to entry]
  E[eject ID] --> L[Look up mapped entry]
  L --> V{Entry still matches array slot?}
  V -- No --> N[Ignore stale ID]
  V -- Yes --> T[Replace handler with null]
  T --> D{Iteration active?}
  D -- No --> C[Trim trailing null handlers]
  D -- Yes --> F[Defer trimming]
  F --> X[Outermost forEach finishes]
  X --> C
Loading

Reviews (1): Last reviewed commit: "fix(interceptors): clear entries after i..." | Re-trigger Greptile

@jasonsaayman
jasonsaayman merged commit 712e023 into v1.x Jul 29, 2026
31 checks passed
@jasonsaayman
jasonsaayman deleted the fix/interceptor-handler-compaction branch July 29, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit::fix The PR is related to a bugfix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InterceptorManager sparse array growth

2 participants