Skip to content

Faster resolver: clean code and the backtrack_stack #5187

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

Merged
merged 8 commits into from
Mar 17, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,12 +951,9 @@ fn activate_deps_loop(
&& past_conflicting_activations
.get(&dep)
.and_then(|past_bad| {
past_bad.iter().find(|conflicting| {
conflicting
.iter()
// note: a lot of redundant work in is_active for similar debs
.all(|(con, _)| cx.is_active(con))
})
past_bad
.iter()
.find(|conflicting| cx.is_conflicting(None, conflicting))
})
.is_some();

Expand Down Expand Up @@ -1091,12 +1088,9 @@ fn activate_deps_loop(
.filter_map(|(_, (deb, _, _))| {
Copy link
Member

Choose a reason for hiding this comment

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

I think the filter_map + next combo can be replaced with find

Copy link
Contributor Author

Choose a reason for hiding this comment

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

something like?

.filter_map(|(_, (new_dep, _, _))| past_conflicting_activations.get(&new_dep))
.flat_map(|x| x)
.find(|con| cx.is_conflicting(None, con))

I'd love to find the elegant way to do this, just haven't found it yet.

Copy link
Member

Choose a reason for hiding this comment

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

Oh right yeah, don't worry about this it's fine as-is!

Copy link
Member

Choose a reason for hiding this comment

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

Submitted a PR to the std to make this code prettier :) rust-lang/rust#49098

past_conflicting_activations.get(&deb).and_then(|past_bad| {
Copy link
Member

Choose a reason for hiding this comment

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

This may actually be a great place to use ?:

past_conflicting_activations.get(&deb)?
    .iter()
    .find(|conflicts| cx.is_conflicting(None, conflicts))

(also this may want to rename deb to dep

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So much better!

// for each dependency check all of its cashed conflicts
Copy link
Member

Choose a reason for hiding this comment

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

s/cashed/cached/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks spelling is not my strong suit! I know I'd mess this one up at some point.

Copy link
Member

Choose a reason for hiding this comment

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

oh no worries, it's also not mine!

past_bad.iter().find(|conflicting| {
conflicting
past_bad
.iter()
// note: a lot of redundant work in is_active for similar debs
.all(|(con, _)| cx.is_active(con))
})
.find(|conflicting| cx.is_conflicting(None, conflicting))
})
})
.next()
Expand All @@ -1123,6 +1117,14 @@ fn activate_deps_loop(
conflicting_activations
);
past.push(conflicting_activations.clone());
// also clean the `backtrack_stack` so we don't
// backtrack to a place where we will try this again.
backtrack_stack.retain(|bframe| {
!bframe.context_backup.is_conflicting(
Some(parent.package_id()),
&conflicting_activations,
)
});
}
}
// if not has_another we we activate for the better error messages
Expand Down Expand Up @@ -1195,11 +1197,9 @@ fn find_candidate<'a>(
frame.context_backup.prev_active(&frame.dep),
&frame.context_backup.links,
);
if frame.context_backup.is_active(parent.package_id())
&& conflicting_activations
.iter()
// note: a lot of redundant work in is_active for similar debs
.all(|(con, _)| frame.context_backup.is_active(con))
if frame
.context_backup
.is_conflicting(Some(parent.package_id()), conflicting_activations)
{
continue;
}
Expand Down Expand Up @@ -1643,6 +1643,20 @@ impl Context {
.unwrap_or(false)
}

/// checks whether all of `parent` and the keys of `conflicting activations`
/// are still active
fn is_conflicting(
&self,
parent: Option<&PackageId>,
conflicting_activations: &HashMap<PackageId, ConflictReason>,
) -> bool {
parent.map(|p| self.is_active(p)).unwrap_or(true)
Copy link
Member

Choose a reason for hiding this comment

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

This might be a little tidier as:

conflicting_activations.keys().chain(parent).all(|id| self.is_active(id))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

&& conflicting_activations
.keys()
// note: a lot of redundant work in is_active for similar debs
.all(|con| self.is_active(con))
}

/// Return all dependencies and the features we want from them.
fn resolve_features<'b>(
&mut self,
Expand Down
43 changes: 43 additions & 0 deletions tests/testsuite/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,49 @@ fn resolving_with_many_equivalent_backtracking() {
assert!(res.is_err());
}

#[test]
fn resolving_with_deep_traps() {
let mut reglist = Vec::new();

const DEPTH: usize = 200;
const BRANCHING_FACTOR: usize = 100;

// Each backtrack_trap depends on the next, and adds a backtrack frame.
// None of witch is going to help with `bad`.
for l in 0..DEPTH {
let name = format!("backtrack_trap{}", l);
let next = format!("backtrack_trap{}", l + 1);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep_req(next.as_str(), "*")]));
}
}
{
let name = format!("backtrack_trap{}", DEPTH);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str())));
}
}
{
// slightly less constrained to make sure `cloaking` gets picked last.
for i in 1..(BRANCHING_FACTOR + 10) {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!(("cloaking", vsn.as_str()) => [dep_req("bad", "1.0.1")]));
}
}

let reg = registry(reglist.clone());

let res = resolve(
&pkg_id("root"),
vec![dep_req("backtrack_trap0", "*"), dep_req("cloaking", "*")],
&reg,
);

assert!(res.is_err());
}

#[test]
fn resolving_with_constrained_sibling_backtrack_activation() {
// It makes sense to resolve most-constrained deps first, but
Expand Down