Skip to content

Reimplement endorsement of strict version constraints#34893

Merged
ljacomet merged 3 commits into
masterfrom
jvandort/dm/engine/reimplement-strict-version-constraints
Oct 5, 2025
Merged

Reimplement endorsement of strict version constraints#34893
ljacomet merged 3 commits into
masterfrom
jvandort/dm/engine/reimplement-strict-version-constraints

Conversation

@jvandort

@jvandort jvandort commented Sep 4, 2025

Copy link
Copy Markdown
Member

Prior to this PR, whenever a node was deselected for any reason, if it had an incoming edge that endorsed strict versions, we would "reselect" the source node of the edge. In this case re-selection means removing all outgoing edges and re-queueing the source node.

See:

private void reselectEndorsingNode() {
if (incomingEdges.size() == 1) {
EdgeState singleEdge = incomingEdges.get(0);
NodeState from = singleEdge.getFrom();
if (singleEdge.getDependencyState().getDependency().isEndorsingStrictVersions()) {
// pass my own component because we are already in the process of re-selecting it
from.reselect();
}
} else {
for (EdgeState incoming : new ArrayList<>(incomingEdges)) {
if (incoming.getDependencyState().getDependency().isEndorsingStrictVersions()) {
// pass my own component because we are already in the process of re-selecting it
incoming.getFrom().reselect();
}
}
}
}
private void reselect() {
resolveState.onMoreSelected(this);
removeOutgoingEdges();
}

If the source node was the root node, that means we would attempt to de-select the root node and add it back to the queue. This effectively restarts the whole graph traversal from the beginning. If it weren't for some shady logic that short-circuits unstable graphs (#29936) this would lead to an infinite loop. However, due the short-circuit we instead produced a mostly correct graph, but sometimes produced incorrect graphs. This occasionally led to critical failures in the resolution engine.

In this PR, we re-implement the way strict versions are inherited between nodes in a dependency graph. Strict versions are now handled much more similarly to how exclusions are propagated down a dependency graph.

Now, when visiting a node's dependencies, we compute the inherited (ancestor's) strict versions for that node on-demand. We compare the value to the pervious traversal's inherited strict versions, propagating any changes to existing outgoing edges.

When adding or removing edges from nodes, we use the endorsing-ness of the edge to determine if additional nodes need to be re-queued to account for changes from endorsed dependencies.

This new approach allows us to avoid the potential of reprocessing the root node, an operation which currently can lead to buggy graphs.

Fixes #33508

Reviewing cheatsheet

Before merging the PR, comments starting with

  • ❌ ❓must be fixed
  • 🤔 💅 should be fixed
  • 💭 may be fixed
  • 🎉 celebrate happy things

@jvandort jvandort force-pushed the jvandort/dm/engine/reimplement-strict-version-constraints branch 4 times, most recently from 8152fb5 to e2418c7 Compare September 5, 2025 22:52
@jvandort

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@jvandort jvandort requested a review from ljacomet September 5, 2025 22:52
@jvandort jvandort self-assigned this Sep 5, 2025
@jvandort jvandort added this to the 9.2.0 RC1 milestone Sep 5, 2025
@bot-gradle

This comment has been minimized.

@ljacomet ljacomet changed the title Reimplement strict version constraints Reimplement endorsement of strict version constraints Sep 9, 2025
@@ -207,9 +204,7 @@ private void traverseGraph(final ResolveState resolveState) {
// Initialize and collect any new outgoing edges of this node
dependencies.clear();
node.visitOutgoingDependencies(dependencies);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

💅 Since we are cleaning the logic here, maybe we could continue cleaning up names: dependencies is really edges - and it becomes that deeper down.
visitOutgoingDependencies could be visitOutgoingDependenciesAndCollectEdges - a mouthful bu that's what it does.

@jvandort jvandort force-pushed the jvandort/dm/engine/reimplement-strict-version-constraints branch from 1b963ef to 7d51483 Compare September 11, 2025 15:52
@jvandort

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@jvandort

Copy link
Copy Markdown
Member Author

@jvandort jvandort modified the milestones: 9.2.0 RC1, 9.3.0 RC1 Sep 19, 2025
@jvandort jvandort force-pushed the jvandort/dm/engine/reimplement-strict-version-constraints branch from 7d51483 to 7c071f9 Compare September 21, 2025 23:06
@jvandort

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@jvandort

This comment has been minimized.

@bot-gradle

This comment has been minimized.

@jvandort jvandort marked this pull request as ready for review September 21, 2025 23:39
@jvandort jvandort requested a review from a team as a code owner September 21, 2025 23:39
@jvandort jvandort requested review from octylFractal and removed request for octylFractal September 21, 2025 23:39
@bot-gradle

Copy link
Copy Markdown
Collaborator

The following builds have failed:

@bot-gradle

Copy link
Copy Markdown
Collaborator

The following builds have failed:

@jvandort

Copy link
Copy Markdown
Member Author

@bot-gradle test apt prf

@bot-gradle

This comment has been minimized.

@bot-gradle

Copy link
Copy Markdown
Collaborator

The following builds have passed:

@bot-gradle

Copy link
Copy Markdown
Collaborator

The following builds have passed:

@jvandort

Copy link
Copy Markdown
Member Author

We still need to add a test demonstrating fixed behavior

@jvandort jvandort force-pushed the jvandort/dm/engine/reimplement-strict-version-constraints branch from a33f88c to d54da30 Compare September 23, 2025 03:31
Prior to this commit, whenever a node was deselected for any reason, if it had an incoming edge that endorsed strict versions,
we would "reselect" the source node of the edge. In this case re-selection means removing all outgoing edges and re-queueing
 the source node.

If the source node was the root node, that means we would attempt to de-select the root node and add it back to the queue.
This effectively restarts the whole graph traversal from the beginning. If it weren't for some shady logic that short-circuits unstable graphs,
this would lead to an infinite loop. However, due the short-circuit we instead produced a mostly correct graph, but sometimes produced
incorrect graphs. This occasionally led to critical failures in the resolution engine.

In this commit, we re-implement the way strict versions are inherited between nodes in a dependency graph.
Strict versions are now handled much more similarly to how exclusions are propagated down a dependency graph.

Now, when visiting a node's dependencies, we compute the inherited (ancestor's) strict versions for that node on-demand.
We compare the value to the pervious traversal's inherited strict versions, propagating any changes to existing outgoing edges.
When adding or removing edges from nodes, we use the endorsing-ness of the edge to determine if additional nodes need to be
re-queued to account for changes from endorsed dependencies.

This reduces pressure on the unstable-graph-short-circuit logic, avoiding potential for broken graphs when using endorsed strict versions.

Additionally, we:
- Rework resolveEdges to run in a single pass instead of multiple passes, by instead recomputing selectors in visitDependencies
- Improve computeSelector by properly calling use/release on the old/new selectors, ensuring the selector properly tracks the number of edges using it
- Update dependency visiting logic to pass-down computed exclusions and ancestors strict versions instead of relying on state, ensuring it is more difficult to accidentally use stale computed state
- Rename methods, fields, variables as needed. Add Javadoc and TODOs as needed. Reactor methods as needed for readability.
Calculating these endorsed strict versions for each parent of a node each time a node is processed is expensive.
Instead, we cache the result of this computation and ensure we invalidate it each time an input to the calculation is changed.
@jvandort jvandort force-pushed the jvandort/dm/engine/reimplement-strict-version-constraints branch from d54da30 to e7b27b5 Compare September 25, 2025 01:06
@autonomousapps

Copy link
Copy Markdown
Contributor

I can confirm that this resolves at least one of the issues in Block's cash-server build.

@jvandort jvandort requested a review from ljacomet October 2, 2025 03:51
This reproducer is mostly minified from the original reproducer, though could likey be reduced down more.
@ljacomet ljacomet force-pushed the jvandort/dm/engine/reimplement-strict-version-constraints branch from 5ba2fba to 506e719 Compare October 5, 2025 16:24

@ljacomet ljacomet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

note that I moved the test according to the TODO it had

@ljacomet ljacomet added this pull request to the merge queue Oct 5, 2025
Merged via the queue into master with commit f02ee38 Oct 5, 2025
28 checks passed
@ljacomet ljacomet deleted the jvandort/dm/engine/reimplement-strict-version-constraints branch October 5, 2025 17:35
@cobexer cobexer mentioned this pull request Mar 18, 2026
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.

Mysterious "Problems reading data from Binary store"

4 participants