-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fast path for incompatible deps #6919
Conversation
r? @ehuss (rust_highfive has picked a reviewer for you, use r? to override) |
So after posting this I realized that the proptests have a size argument. If it can't find a slow case in Indexes of 50 crates etch with 20 versions then we can try Indexes of 70 crates etch with 30 versions. Indeed that found a failing case, I am working on minimizing. |
I have given up on finding a clear test case for the original optimization, and just added it back in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so trying to understand the current purpose of the PR, it sounds like we have a general system for handling this sort of backtracking but what's added here is a more specialized version for just Cargo's problem? That feels wrong though so I'm curious if you've got a refresher you could give me :)
src/cargo/core/resolver/mod.rs
Outdated
None | ||
}; | ||
|
||
let our_links: Option<HashSet<_>> = our_candidates.iter().map(|c| c.summary.links()).collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be more clear to use filter_map
and then collect directly into HashSet
instead of Option<HashSet>
perhaps?
src/cargo/core/resolver/mod.rs
Outdated
con.extend(other.iter().map(|(&id, re)| (id, re.clone()))); | ||
if (our_activation_key.map_or(false, |our| { | ||
other.summary.package_id().as_activations_key() == our | ||
}) || our_link.map_or(false, |_| other.summary.links() == our_link)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of extracting a single element above would it be perhaps more clear to change these to:
if our_activation_keys.len() == 1 && our_activation_keys.contains(&other.summary.package_id().as_activations_key()) {
// ..
}
or something like that?
src/cargo/core/resolver/mod.rs
Outdated
// A + B is active then A is active and we know that is not ok. | ||
for (_, other) in &others { | ||
con.extend(other.iter().map(|(&id, re)| (id, re.clone()))); | ||
if (our_activation_key.map_or(false, |our| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this if
have a comment indicating what it's doing? (aka why it's skipping over things if these sets are single element and we're the only element in there)
src/cargo/core/resolver/mod.rs
Outdated
} else { | ||
cx.is_active(id).filter(|&age| | ||
// we only care about things that are older then critical_age | ||
age < backtrack_critical_age) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe in need of some rustfmt
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have fmt on save, but it seems to be some kind of fmt bug. If the comment is in the callback fmt will not move it.
past_conflicting_activations.insert(dep, &con); | ||
return Some(con); | ||
|
||
continue 'dep; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could there be a comment here indicating why we're breaking to the outer loop and cancelling this part of the search?
src/cargo/core/resolver/mod.rs
Outdated
); | ||
println!("used new {}", other.summary.version()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stray println
src/cargo/core/resolver/mod.rs
Outdated
past_conflicting_activations.find( | ||
new_dep, | ||
&|id| { | ||
if id == candidate.package_id() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems pretty similar to the code above, so maybe an implementation could be shared?
ef858d8
to
f372b11
Compare
Added a bunch of comments that I think address your points, and updated the OP. |
☔ The latest upstream changes (presumably #6946) made this pull request unmergeable. Please resolve the merge conflicts. |
The more I think about it the more that is accurate, with one small tweak. "backtracking" is technically the job of
So the job of the resolver is to select a set of crates that meat some constraints. (There should probably be a list.) Some constraints are enforced by the structure of the project. For example the constraint "each activated versions dependencies are satisfied" is enforced by adding the list of dependencies to the Other constraints are enforced by a check before activation. For example the constraint "No two activated versions are semver compatible" is enforced by an assert in This leads to "learned clauses" or what does this have to do with So if we already have the infrastructure to ban a
Pre #6776, we only make the But, a small amount of noting a pattern can make things exponentially better. We want to combine previously discovered conflict to make a more general one. There are a staggering number of The insight of #6776 was to sort the packages by how far back we can "backtrack" and only try to combine with the one that is newest. If we were able to prove that one of the others was not needed, we would still "backtrack" to the same place, so that generalization is not likely to be helpful. Then we can try to combine it with the constraint that the dependency that required that package needs to resolve to something. If we have already stored a The problem, we only store a conflict for things we have tried. We have to, as there are an almost infinite number of conflicts for things we haven't tried. One reason we may not have tried a version is that one of its dependencies is not going to work, so this PR adds a check for that. The fuzzer found a more annoying reason. let's say we have to resolve There is probably some general way to combine things, but I don't see it. But there is something really simple about this case, it is really fast to see that This hack is yet another place we enforce the semver constraint, and is a special case not a general rule, but |
☔ The latest upstream changes (presumably #7011) made this pull request unmergeable. Please resolve the merge conflicts. |
This is not going to get the attention that is needed to merge. And at this point I'd rather see the effort go into PubGrub not into optimizing this resolver. A lot of ink has gone into it, a more responsible maintainer would turn it into documentation before closing. But I am not that responsible. |
This adds two targeted fast paths that happen to cover the cases in #6283, to the general tool made in #6776 and improved in #6910. This gets all the tests to pass. Including all of the seeds reported at #6258, even #6258 (comment) that is failing on master.