Skip to content

fix(npm): memoize peer cache hit checks to prevent combinatorial explosion#32609

Merged
bartlomieju merged 3 commits intomainfrom
fix/npm-peer-resolution-hang
Mar 10, 2026
Merged

fix(npm): memoize peer cache hit checks to prevent combinatorial explosion#32609
bartlomieju merged 3 commits intomainfrom
fix/npm-peer-resolution-hang

Conversation

@bartlomieju
Copy link
Copy Markdown
Member

Summary

  • Fixes deno add npm:@aws-cdk/aws-ecs (and similar large peer dep trees) hanging indefinitely
  • Root cause: find_peers_cache_hit_inner in libs/npm/resolution/graph.rs recursively checks whether cached peer resolutions match the current context. For packages with many mutual peer dependencies (like AWS CDK v1 with ~33 packages and 1200+ cache entries), this causes combinatorial explosion — a single cache lookup for @aws-cdk/aws-kms triggered 2.5 million recursive calls taking 17+ seconds
  • Fix: since parent_pkgs is constant within a single find_peers_cache_hit call, the answer for any given nv never changes. A HashMap memo eliminates all redundant recursive work

Before/After

Before After
deno add npm:@aws-cdk/aws-ecs Hangs indefinitely (killed at 120s) Completes in seconds

Test plan

  • All 182 deno_npm unit tests pass
  • Manual test: deno add npm:@aws-cdk/aws-ecs completes successfully

Closes #26430

🤖 Generated with Claude Code

…osion

When resolving peer dependencies for packages with many mutual peer deps
(e.g. AWS CDK v1), `find_peers_cache_hit_inner` was called recursively
millions of times. Since `parent_pkgs` is constant within a single
`find_peers_cache_hit` call, the result for any given `nv` is always the
same. Adding a memo HashMap eliminates redundant recursive checks, turning
`deno add npm:@aws-cdk/aws-ecs` from hanging indefinitely to completing
in seconds.

Closes #26430

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@kajukitli kajukitli left a comment

Choose a reason for hiding this comment

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

lgtm, classic memoization fix for exponential blowup

the key insight is correct: parent_pkgs is constant for the entire call tree, so once you know whether nv X has a matching cache entry, that answer won't change. memoizing it collapses O(2^n) back to O(n).

good that you don't memoize the cycle-detection early return (checking.insert returns false) — that result is path-dependent.

2.5M calls → memoized lookups is a nice win 👍

Copy link
Copy Markdown
Contributor

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Contributor

@dsherret dsherret left a comment

Choose a reason for hiding this comment

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

We should get a test for this before merging. Ideally this would be a test in deno_npm.

Adds a test with 25 mutually peer-dependent packages that hangs
indefinitely without the memoization fix but completes in <1s with it.

Regression test for #26430

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
/// millions of redundant recursive calls and hangs indefinitely.
/// Regression test for https://github.com/denoland/deno/issues/26430
#[tokio::test]
async fn peer_deps_mutual_many_packages_no_hang() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I confirmed this test hangs on main and burns 100% CPU. It passes fine in this PR.

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.

Great!

Copy link
Copy Markdown
Contributor

@dsherret dsherret left a comment

Choose a reason for hiding this comment

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

LGTM!

@bartlomieju bartlomieju merged commit ed367f2 into main Mar 10, 2026
112 checks passed
@bartlomieju bartlomieju deleted the fix/npm-peer-resolution-hang branch March 10, 2026 17:50
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.

deno add npm:@aws-cdk/aws-ecs appears to hang forever

4 participants