Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

reekystive
Copy link
Contributor

The Issue

Changes

ExtendedDeferred

interface ExtendedDeferred extends pDefer.DeferredPromise<DepPath> {
  alias?: string // alias this nodeId represents
  waitingFor?: Set<string> // aliases it is still waiting for
}

Populate metadata

  • When creating a new Deferred, attach alias ('unknown' is fine until we meet the real one) and an empty waitingFor set.
  • Whenever a node collects pendingPeers, copy those aliases into its own waitingFor.

Mutual-wait detection in calculateDepPath()

// A waits for B; if B.waitingFor has A, resolve B immediately
const peerIsWaitingMe = peerDeferred?.waitingFor?.has(currentAlias)
if (peerIsWaitingMe) {
  peerDeferred!.resolve(id)
  return id
}

This breaks 2-node (or longer) dead-locks without relying on the graph.

Keep existing cycle handling (cyclicPeerAliases) for larger cycles.

Cleanup

  • Dropped all temporary debugLog/DEBUG_PEERS code.
  • Translated remaining comments to English.

Impact

Tests

  • Local reproduction repo (alias + peer loop) now installs successfully.
  • A new unit test can be added later to cover the mutual-wait scenario (similar to the existing aliased-peer test).

Related

@reekystive reekystive requested a review from zkochan as a code owner June 20, 2025 13:42
Copy link

welcome bot commented Jun 20, 2025

💖 Thanks for opening this pull request! 💖
Please be patient and we will get back to you as soon as we can.

@reekystive reekystive changed the title Fix issue #9673: break mutual-wait dead-lock introduced after #8760 Fix #9673: break mutual-wait dead-lock introduced in #8760 Jun 20, 2025
@zkochan
Copy link
Member

zkochan commented Jun 21, 2025

If you are saying the cycle is not detected because the real package name is not taken into account, then won't this fix the issue?

    const node = ctx.dependenciesTree.get(childNodeId)!
    graph.push([node.resolvedPackage.name, edges])
    graph.push([currentAlias, edges])

Here:
https://github.com/pnpm/pnpm/blob/0e596f60b8a1d6725463c145327d2b5a0017ef36/pkg-manager/resolve-dependencies/src/resolvePeers.ts#L847`

@reekystive
Copy link
Contributor Author

reekystive commented Jun 21, 2025

I tried applying the suggested change locally, but unfortunately it doesn't seem to resolve the issue. Running a reproducible case still results in the same error that was originally fixed by #8760:

diff --git a/pkg-manager/resolve-dependencies/src/resolvePeers.ts b/pkg-manager/resolve-dependencies/src/resolvePeers.ts
index 07830ddd1..77b19dddb 100644
--- a/pkg-manager/resolve-dependencies/src/resolvePeers.ts
+++ b/pkg-manager/resolve-dependencies/src/resolvePeers.ts
@@ -844,6 +844,8 @@ async function resolvePeersOfChildren<T extends PartialResolvedPackage> (
       allResolvedPeers.set(peerName, peerNodeId)
       edges.push(peerName)
     }
+    const node = ctx.dependenciesTree.get(childNodeId)!
+    graph.push([node.resolvedPackage.name, edges])
     graph.push([currentAlias, edges])
     for (const [missingPeer, range] of missingPeers.entries()) {
       allMissingPeers.set(missingPeer, range)
pnpm: Duplicate edge specification from "react-dom"
...
    at resolvePeersOfChildren (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:855:24)
    at async resolvePeersOfNode (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:467:7)
    at async resolvePeersOfChildren (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:835:9)
    at async resolvePeersOfNode (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:467:7)
    at async resolvePeersOfChildren (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:835:9)
    at async resolvePeers (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:123:27)

The issue seems to involve a peer layout where an alias and the actual package name depend on each other. Since only one directional edge is retained in the graph, graph-cycles doesn't detect the cycle, and both sides end up mutually waiting.

That said, I'm not deeply familiar with the overall structure of the pnpm codebase, so it's very possible I'm missing something.

Thanks for taking a look!

@reekystive reekystive changed the title Fix #9673: break mutual-wait dead-lock introduced in #8760 fix(#9673): break mutual-wait dead-lock introduced in #8760 Jun 21, 2025
@zkochan
Copy link
Member

zkochan commented Jun 21, 2025

pnpm: Duplicate edge specification from "react-dom"

This should work then

const node = ctx.dependenciesTree.get(childNodeId)!
graph.push([node.resolvedPackage.name, edges])
if (currentAlias !== node.resolvedPackage.name) {
     graph.push([currentAlias, edges])
}

It would be really good to cover this with a test. Can you share the reproduction? If it uses private packages, we can simulate the same dependency graph with our registry mock: https://github.com/pnpm/registry-mock

@reekystive
Copy link
Contributor Author

That doesn't work either.

pnpm: Duplicate edge specification from "@editor-kit/plugins"
    at <anonymous> (.../.pnpm-store/v10/links/graph-cycles/1.2.1/6f2b25f857fcf85625df3e696ef321b4f88cc829daec112b0865999e9edd275c/node_modules/graph-cycles/dist/index.js:27:19)
    at Array.forEach (<anonymous>)
    at buildAndEnsureValidGraph (.../.pnpm-store/v10/links/graph-cycles/1.2.1/6f2b25f857fcf85625df3e696ef321b4f88cc829daec112b0865999e9edd275c/node_modules/graph-cycles/dist/index.js:25:11)
    at analyzeGraph2 (.../.pnpm-store/v10/links/graph-cycles/1.2.1/6f2b25f857fcf85625df3e696ef321b4f88cc829daec112b0865999e9edd275c/node_modules/graph-cycles/dist/index.js:33:22)
    at resolvePeersOfChildren (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:857:24)
    at async resolvePeers (.../pnpm/pkg-manager/resolve-dependencies/src/resolvePeers.ts:123:27)
    at async resolveDependencies2 (.../pnpm/pkg-manager/resolve-dependencies/src/index.ts:202:7)
    at async _installInContext (.../pnpm/pkg-manager/core/src/install/index.ts:1129:7)
    at async installInContext (.../pnpm/pkg-manager/core/src/install/index.ts:1628:12)
    at async _install (.../pnpm/pkg-manager/core/src/install/index.ts:633:20)

@reekystive
Copy link
Contributor Author

I successfully used @pnpm/registry-mock to publish packages to the mock registry locally after removing unrelated files, and reproduced the deadlock against the local mock registry.

How should I provide this reproduction? Is there any guideline I should follow?

@reekystive
Copy link
Contributor Author

Here's the reproduction repo: https://github.com/reekystive/pnpm-peers-deadlock-issue

@zkochan
Copy link
Member

zkochan commented Jun 23, 2025

Submit your packages to the registry-mock repository, then I'll publish it and you'll be able to write a test for it in this PR.

@zkochan
Copy link
Member

zkochan commented Jun 26, 2025

I am adding the test

@zkochan zkochan merged commit 260f1ca into pnpm:main Jun 26, 2025
11 checks passed
Copy link

welcome bot commented Jun 26, 2025

Congrats on merging your first pull request! 🎉🎉🎉

@reekystive
Copy link
Contributor Author

Thanks for fixing this!

Our team relies heavily on pnpm every day, thank you for all the hard work and thoughtful design that make it such a dependable tool. We’re very grateful! ❤️

zkochan added a commit that referenced this pull request Jun 26, 2025
@zkochan
Copy link
Member

zkochan commented Jun 27, 2025

Thanks for providing the reproduction packages. Without that it is really hard to fix such issues.

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.

Installation hangs indefinitely when resolving peers in pnpm 9.13.2 – 10.12.1 (regression of #8760) Duplicate edge specification from "package"
2 participants