Skip to content
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

feat: allow cancelling the solving process #20

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 12 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ pub use internal::{
};
pub use pool::Pool;
pub use solvable::Solvable;
pub use solver::{Solver, SolverCache};
pub use solver::{Solver, SolverCache, UnsolvableOrCancelled};
use std::{
any::Any,
fmt::{Debug, Display},
hash::Hash,
};
Expand Down Expand Up @@ -75,6 +76,16 @@ pub trait DependencyProvider<VS: VersionSet, N: PackageName = String>: Sized {

/// Returns the dependencies for the specified solvable.
fn get_dependencies(&self, solvable: SolvableId) -> Dependencies;

/// Whether the solver should stop the dependency resolution algorithm.
///
/// This method gets called at the beginning of each unit propagation round and before
/// potentially blocking operations (like [Self::get_dependencies] and [Self::get_candidates]).
/// If it returns `Some(...)`, the solver will stop and return
/// [UnsolvableOrCancelled::Cancelled].
fn should_cancel_with_value(&self) -> Option<Box<dyn Any>> {
None
}
}

/// A list of candidate solvables for a specific package. This is returned from
Expand Down
4 changes: 3 additions & 1 deletion src/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ impl Problem {
&Clause::Requires(package_id, version_set_id) => {
let package_node = Self::add_node(&mut graph, &mut nodes, package_id);

let candidates = solver.cache.get_or_cache_sorted_candidates(version_set_id);
let candidates = solver.cache.get_or_cache_sorted_candidates(version_set_id).unwrap_or_else(|_| {
unreachable!("The version set was used in the solver, so it must have been cached. Therefore cancellation is impossible here and we cannot get an `Err(...)`")
});
if candidates.is_empty() {
tracing::info!(
"{package_id:?} requires {version_set_id:?}, which has no candidates"
Expand Down
89 changes: 67 additions & 22 deletions src/solver/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ use crate::{
};
use bitvec::vec::BitVec;
use elsa::FrozenMap;
use std::any::Any;
use std::cell::RefCell;
use std::marker::PhantomData;

/// Keeps a cache of previously computed and/or requested information about solvables and version
/// sets.
pub struct SolverCache<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> {
provider: D,
pub(crate) provider: D,

/// A mapping from package name to a list of candidates.
candidates: Arena<CandidatesId, Candidates>,
Expand Down Expand Up @@ -70,11 +71,24 @@ impl<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> SolverCache<V

/// Returns the candidates for the package with the given name. This will either ask the
/// [`DependencyProvider`] for the entries or a cached value.
pub fn get_or_cache_candidates(&self, package_name: NameId) -> &Candidates {
///
/// If the provider has requested the solving process to be cancelled, the cancellation value
/// will be returned as an `Err(...)`.
pub fn get_or_cache_candidates(
&self,
package_name: NameId,
) -> Result<&Candidates, Box<dyn Any>> {
// If we already have the candidates for this package cached we can simply return
let candidates_id = match self.package_name_to_candidates.get_copy(&package_name) {
Some(id) => id,
None => {
// Since getting the candidates from the provider is a potentially blocking
// operation, we want to check beforehand whether we should cancel the solving
// process
if let Some(value) = self.provider.should_cancel_with_value() {
return Err(value);
}

// Otherwise we have to get them from the DependencyProvider
let candidates = self
.provider
Expand Down Expand Up @@ -105,17 +119,23 @@ impl<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> SolverCache<V
};

// Returns a reference from the arena
&self.candidates[candidates_id]
Ok(&self.candidates[candidates_id])
}

/// Returns the candidates of a package that match the specified version set.
pub fn get_or_cache_matching_candidates(&self, version_set_id: VersionSetId) -> &[SolvableId] {
///
/// If the provider has requested the solving process to be cancelled, the cancellation value
/// will be returned as an `Err(...)`.
pub fn get_or_cache_matching_candidates(
&self,
version_set_id: VersionSetId,
) -> Result<&[SolvableId], Box<dyn Any>> {
match self.version_set_candidates.get(&version_set_id) {
Some(candidates) => candidates,
Some(candidates) => Ok(candidates),
None => {
let package_name = self.pool().resolve_version_set_package_name(version_set_id);
let version_set = self.pool().resolve_version_set(version_set_id);
let candidates = self.get_or_cache_candidates(package_name);
let candidates = self.get_or_cache_candidates(package_name)?;

let matching_candidates = candidates
.candidates
Expand All @@ -127,23 +147,27 @@ impl<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> SolverCache<V
})
.collect();

self.version_set_candidates
.insert(version_set_id, matching_candidates)
Ok(self
.version_set_candidates
.insert(version_set_id, matching_candidates))
}
}
}

/// Returns the candidates that do *not* match the specified requirement.
///
/// If the provider has requested the solving process to be cancelled, the cancellation value
/// will be returned as an `Err(...)`.
pub fn get_or_cache_non_matching_candidates(
&self,
version_set_id: VersionSetId,
) -> &[SolvableId] {
) -> Result<&[SolvableId], Box<dyn Any>> {
match self.version_set_inverse_candidates.get(&version_set_id) {
Some(candidates) => candidates,
Some(candidates) => Ok(candidates),
None => {
let package_name = self.pool().resolve_version_set_package_name(version_set_id);
let version_set = self.pool().resolve_version_set(version_set_id);
let candidates = self.get_or_cache_candidates(package_name);
let candidates = self.get_or_cache_candidates(package_name)?;

let matching_candidates = candidates
.candidates
Expand All @@ -155,23 +179,30 @@ impl<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> SolverCache<V
})
.collect();

self.version_set_inverse_candidates
.insert(version_set_id, matching_candidates)
Ok(self
.version_set_inverse_candidates
.insert(version_set_id, matching_candidates))
}
}
}

/// Returns the candidates for the package with the given name similar to
/// [`Self::get_or_cache_candidates`] sorted from highest to lowest.
pub fn get_or_cache_sorted_candidates(&self, version_set_id: VersionSetId) -> &[SolvableId] {
///
/// If the provider has requested the solving process to be cancelled, the cancellation value
/// will be returned as an `Err(...)`.
pub fn get_or_cache_sorted_candidates(
&self,
version_set_id: VersionSetId,
) -> Result<&[SolvableId], Box<dyn Any>> {
match self.version_set_to_sorted_candidates.get(&version_set_id) {
Some(canidates) => canidates,
Some(candidates) => Ok(candidates),
None => {
let package_name = self.pool().resolve_version_set_package_name(version_set_id);
let matching_candidates = self.get_or_cache_matching_candidates(version_set_id);
let candidates = self.get_or_cache_candidates(package_name);
let matching_candidates = self.get_or_cache_matching_candidates(version_set_id)?;
let candidates = self.get_or_cache_candidates(package_name)?;

// Sort all the candidates in order in which they should betried by the solver.
// Sort all the candidates in order in which they should be tried by the solver.
let mut sorted_candidates = Vec::new();
sorted_candidates.extend_from_slice(matching_candidates);
self.provider.sort_candidates(self, &mut sorted_candidates);
Expand All @@ -185,18 +216,32 @@ impl<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> SolverCache<V
}
}

self.version_set_to_sorted_candidates
.insert(version_set_id, sorted_candidates)
Ok(self
.version_set_to_sorted_candidates
.insert(version_set_id, sorted_candidates))
}
}
}

/// Returns the dependencies of a solvable. Requests the solvables from the
/// [`DependencyProvider`] if they are not known yet.
pub fn get_or_cache_dependencies(&self, solvable_id: SolvableId) -> &Dependencies {
///
/// If the provider has requested the solving process to be cancelled, the cancellation value
/// will be returned as an `Err(...)`.
pub fn get_or_cache_dependencies(
&self,
solvable_id: SolvableId,
) -> Result<&Dependencies, Box<dyn Any>> {
let dependencies_id = match self.solvable_to_dependencies.get_copy(&solvable_id) {
Some(id) => id,
None => {
// Since getting the dependencies from the provider is a potentially blocking
// operation, we want to check beforehand whether we should cancel the solving
// process
if let Some(value) = self.provider.should_cancel_with_value() {
return Err(value);
}

let dependencies = self.provider.get_dependencies(solvable_id);
let dependencies_id = self.solvable_dependencies.alloc(dependencies);
self.solvable_to_dependencies
Expand All @@ -205,7 +250,7 @@ impl<VS: VersionSet, N: PackageName, D: DependencyProvider<VS, N>> SolverCache<V
}
};

&self.solvable_dependencies[dependencies_id]
Ok(&self.solvable_dependencies[dependencies_id])
}

/// Returns true if the dependencies for the given solvable are "cheaply" available. This means
Expand Down
Loading