resolvo/solver/
cache.rs

1use std::{any::Any, cell::RefCell, rc::Rc};
2
3use ahash::HashMap;
4use bitvec::vec::BitVec;
5use elsa::FrozenMap;
6use event_listener::Event;
7
8use crate::{
9    Candidates, Dependencies, DependencyProvider, HintDependenciesAvailable, NameId, Requirement,
10    SolvableId, VersionSetId,
11    internal::{
12        arena::{Arena, ArenaId},
13        frozen_copy_map::FrozenCopyMap,
14        id::{CandidatesId, DependenciesId},
15    },
16};
17
18/// Keeps a cache of previously computed and/or requested information about
19/// solvables and version sets.
20pub struct SolverCache<D: DependencyProvider> {
21    provider: D,
22
23    /// A mapping from package name to a list of candidates.
24    candidates: Arena<CandidatesId, Candidates>,
25    package_name_to_candidates: FrozenCopyMap<NameId, CandidatesId>,
26    package_name_to_candidates_in_flight: RefCell<HashMap<NameId, Rc<Event>>>,
27
28    /// A mapping of `VersionSetId` to the candidates that match that set.
29    version_set_candidates: FrozenMap<VersionSetId, Vec<SolvableId>, ahash::RandomState>,
30
31    /// A mapping of `VersionSetId` to the candidates that do not match that set
32    /// (only candidates of the package indicated by the version set are
33    /// included).
34    version_set_inverse_candidates: FrozenMap<VersionSetId, Vec<SolvableId>, ahash::RandomState>,
35
36    /// A mapping of [`Requirement`] to a sorted list of candidates that fulfill
37    /// that requirement.
38    requirement_to_sorted_candidates: FrozenMap<Requirement, Vec<SolvableId>, ahash::RandomState>,
39
40    /// A mapping from a solvable to a list of dependencies
41    solvable_dependencies: Arena<DependenciesId, Dependencies>,
42    solvable_to_dependencies: FrozenCopyMap<SolvableId, DependenciesId>,
43
44    /// A mapping that indicates that the dependencies for a particular solvable
45    /// can cheaply be retrieved from the dependency provider. This
46    /// information is provided by the DependencyProvider when the
47    /// candidates for a package are requested.
48    hint_dependencies_available: RefCell<BitVec>,
49}
50
51impl<D: DependencyProvider> SolverCache<D> {
52    /// Constructs a new instance from a provider.
53    pub fn new(provider: D) -> Self {
54        Self {
55            provider,
56            candidates: Default::default(),
57            package_name_to_candidates: Default::default(),
58            package_name_to_candidates_in_flight: Default::default(),
59            version_set_candidates: Default::default(),
60            version_set_inverse_candidates: Default::default(),
61            requirement_to_sorted_candidates: Default::default(),
62            solvable_dependencies: Default::default(),
63            solvable_to_dependencies: Default::default(),
64            hint_dependencies_available: Default::default(),
65        }
66    }
67
68    /// Returns the [`DependencyProvider`] used by this cache.
69    pub fn provider(&self) -> &D {
70        &self.provider
71    }
72
73    /// Returns the candidates for the package with the given name. This will
74    /// either ask the [`DependencyProvider`] for the entries or a cached
75    /// value.
76    ///
77    /// If the provider has requested the solving process to be cancelled, the
78    /// cancellation value will be returned as an `Err(...)`.
79    pub async fn get_or_cache_candidates(
80        &self,
81        package_name: NameId,
82    ) -> Result<&Candidates, Box<dyn Any>> {
83        // If we already have the candidates for this package cached we can simply
84        // return
85        let candidates_id = match self.package_name_to_candidates.get_copy(&package_name) {
86            Some(id) => id,
87            None => {
88                // Since getting the candidates from the provider is a potentially blocking
89                // operation, we want to check beforehand whether we should cancel the solving
90                // process
91                if let Some(value) = self.provider.should_cancel_with_value() {
92                    return Err(value);
93                }
94
95                // Check if there is an in-flight request
96                let in_flight_request = self
97                    .package_name_to_candidates_in_flight
98                    .borrow()
99                    .get(&package_name)
100                    .cloned();
101                match in_flight_request {
102                    Some(in_flight) => {
103                        // Found an in-flight request, wait for that request to finish and return
104                        // the computed result.
105                        in_flight.listen().await;
106                        self.package_name_to_candidates
107                            .get_copy(&package_name)
108                            .expect("after waiting for a request the result should be available")
109                    }
110                    None => {
111                        // Prepare an in-flight notifier for other requests coming in.
112                        self.package_name_to_candidates_in_flight
113                            .borrow_mut()
114                            .insert(package_name, Rc::new(Event::new()));
115
116                        // Otherwise we have to get them from the DependencyProvider
117                        let candidates = self
118                            .provider
119                            .get_candidates(package_name)
120                            .await
121                            .unwrap_or_default();
122
123                        // Store information about which solvables dependency information is easy to
124                        // retrieve.
125                        {
126                            let mut hint_dependencies_available =
127                                self.hint_dependencies_available.borrow_mut();
128                            let dependencies_available_candidates =
129                                match &candidates.hint_dependencies_available {
130                                    HintDependenciesAvailable::None => &candidates.candidates[0..0],
131                                    HintDependenciesAvailable::All => &candidates.candidates,
132                                    HintDependenciesAvailable::Some(candidates) => candidates,
133                                };
134                            for hint_candidate in dependencies_available_candidates.iter() {
135                                let idx = hint_candidate.to_usize();
136                                if hint_dependencies_available.len() <= idx {
137                                    hint_dependencies_available.resize(idx + 1, false);
138                                }
139                                hint_dependencies_available.set(idx, true)
140                            }
141                        }
142
143                        // Allocate an ID so we can refer to the candidates from everywhere
144                        let candidates_id = self.candidates.alloc(candidates);
145                        self.package_name_to_candidates
146                            .insert_copy(package_name, candidates_id);
147
148                        // Remove the in-flight request now that we inserted the result and notify
149                        // any waiters
150                        let notifier = self
151                            .package_name_to_candidates_in_flight
152                            .borrow_mut()
153                            .remove(&package_name)
154                            .expect("notifier should be there");
155                        notifier.notify(usize::MAX);
156
157                        candidates_id
158                    }
159                }
160            }
161        };
162
163        // Returns a reference from the arena
164        Ok(&self.candidates[candidates_id])
165    }
166
167    /// Returns the candidates of a package that match the specified version
168    /// set.
169    ///
170    /// If the provider has requested the solving process to be cancelled, the
171    /// cancellation value will be returned as an `Err(...)`.
172    pub async fn get_or_cache_matching_candidates(
173        &self,
174        version_set_id: VersionSetId,
175    ) -> Result<&[SolvableId], Box<dyn Any>> {
176        match self.version_set_candidates.get(&version_set_id) {
177            Some(candidates) => Ok(candidates),
178            None => {
179                let package_name_id = self.provider.version_set_name(version_set_id);
180
181                tracing::trace!(
182                    "Getting matching candidates for package: {}",
183                    self.provider.display_name(package_name_id)
184                );
185
186                let candidates = self.get_or_cache_candidates(package_name_id).await?;
187                tracing::trace!("Got {:?} matching candidates", candidates.candidates.len());
188
189                let matching_candidates = self
190                    .provider
191                    .filter_candidates(&candidates.candidates, version_set_id, false)
192                    .await;
193
194                tracing::trace!(
195                    "Filtered {:?} matching candidates",
196                    matching_candidates.len()
197                );
198
199                Ok(self
200                    .version_set_candidates
201                    .insert(version_set_id, matching_candidates))
202            }
203        }
204    }
205
206    /// Returns the candidates that do *not* match the specified requirement.
207    ///
208    /// If the provider has requested the solving process to be cancelled, the
209    /// cancellation value will be returned as an `Err(...)`.
210    pub async fn get_or_cache_non_matching_candidates(
211        &self,
212        version_set_id: VersionSetId,
213    ) -> Result<&[SolvableId], Box<dyn Any>> {
214        match self.version_set_inverse_candidates.get(&version_set_id) {
215            Some(candidates) => Ok(candidates),
216            None => {
217                let package_name_id = self.provider.version_set_name(version_set_id);
218
219                tracing::trace!(
220                    "Getting NON-matching candidates for package: {:?}",
221                    self.provider.display_name(package_name_id).to_string()
222                );
223
224                let candidates = self.get_or_cache_candidates(package_name_id).await?;
225                tracing::trace!(
226                    "Got {:?} NON-matching candidates",
227                    candidates.candidates.len()
228                );
229
230                let matching_candidates: Vec<SolvableId> = self
231                    .provider
232                    .filter_candidates(&candidates.candidates, version_set_id, true)
233                    .await
234                    .into_iter()
235                    .collect();
236
237                tracing::trace!(
238                    "Filtered {:?} matching candidates",
239                    matching_candidates.len()
240                );
241
242                Ok(self
243                    .version_set_inverse_candidates
244                    .insert(version_set_id, matching_candidates))
245            }
246        }
247    }
248
249    /// Returns the candidates fulfilling the [`Requirement`] sorted from
250    /// highest to lowest within each version set comprising the
251    /// [`Requirement`].
252    ///
253    /// If the provider has requested the solving process to be cancelled, the
254    /// cancellation value will be returned as an `Err(...)`.
255    pub async fn get_or_cache_sorted_candidates(
256        &self,
257        requirement: Requirement,
258    ) -> Result<&[SolvableId], Box<dyn Any>> {
259        match requirement {
260            Requirement::Single(version_set_id) => {
261                self.get_or_cache_sorted_candidates_for_version_set(version_set_id)
262                    .await
263            }
264            Requirement::Union(version_set_union_id) => {
265                match self.requirement_to_sorted_candidates.get(&requirement) {
266                    Some(candidates) => Ok(candidates),
267                    None => {
268                        let sorted_candidates = futures::future::try_join_all(
269                            self.provider()
270                                .version_sets_in_union(version_set_union_id)
271                                .map(|version_set_id| {
272                                    self.get_or_cache_sorted_candidates_for_version_set(
273                                        version_set_id,
274                                    )
275                                }),
276                        )
277                        .await?
278                        .into_iter()
279                        .flatten()
280                        .copied()
281                        .collect();
282
283                        Ok(self
284                            .requirement_to_sorted_candidates
285                            .insert(requirement, sorted_candidates))
286                    }
287                }
288            }
289        }
290    }
291
292    /// Returns the sorted candidates for a singular version set requirement
293    /// (akin to a [`Requirement::Single`]).
294    pub(crate) async fn get_or_cache_sorted_candidates_for_version_set(
295        &self,
296        version_set_id: VersionSetId,
297    ) -> Result<&[SolvableId], Box<dyn Any>> {
298        let requirement = version_set_id.into();
299        if let Some(candidates) = self.requirement_to_sorted_candidates.get(&requirement) {
300            return Ok(candidates);
301        }
302
303        let package_name_id = self.provider.version_set_name(version_set_id);
304        tracing::trace!(
305            "Getting sorted matching candidates for package: {:?}",
306            self.provider.display_name(package_name_id).to_string()
307        );
308
309        let matching_candidates = self
310            .get_or_cache_matching_candidates(version_set_id)
311            .await?;
312        let candidates = self.get_or_cache_candidates(package_name_id).await?;
313
314        // Sort all the candidates in order in which they should be tried by the solver.
315        let mut sorted_candidates = Vec::with_capacity(matching_candidates.len());
316        sorted_candidates.extend_from_slice(matching_candidates);
317        self.provider
318            .sort_candidates(self, &mut sorted_candidates)
319            .await;
320
321        // If we have a solvable that we favor, we sort that to the front. This ensures
322        // that the version that is favored is picked first.
323        if let Some(favored_id) = candidates.favored {
324            if let Some(pos) = sorted_candidates.iter().position(|&s| s == favored_id) {
325                // Move the element at `pos` to the front of the array
326                sorted_candidates[0..=pos].rotate_right(1);
327            }
328        }
329
330        Ok(self
331            .requirement_to_sorted_candidates
332            .insert(requirement, sorted_candidates))
333    }
334
335    /// Returns the dependencies of a solvable. Requests the solvables from the
336    /// [`DependencyProvider`] if they are not known yet.
337    ///
338    /// If the provider has requested the solving process to be cancelled, the
339    /// cancellation value will be returned as an `Err(...)`.
340    pub async fn get_or_cache_dependencies(
341        &self,
342        solvable_id: SolvableId,
343    ) -> Result<&Dependencies, Box<dyn Any>> {
344        let dependencies_id = match self.solvable_to_dependencies.get_copy(&solvable_id) {
345            Some(id) => id,
346            None => {
347                // Since getting the dependencies from the provider is a potentially blocking
348                // operation, we want to check beforehand whether we should cancel the solving
349                // process
350                if let Some(value) = self.provider.should_cancel_with_value() {
351                    return Err(value);
352                }
353
354                let dependencies = self.provider.get_dependencies(solvable_id).await;
355                let dependencies_id = self.solvable_dependencies.alloc(dependencies);
356                self.solvable_to_dependencies
357                    .insert_copy(solvable_id, dependencies_id);
358                dependencies_id
359            }
360        };
361
362        Ok(&self.solvable_dependencies[dependencies_id])
363    }
364
365    /// Returns true if the dependencies for the given solvable are "cheaply"
366    /// available. This means either the dependency provider indicated that
367    /// the dependencies for a solvable are available or the dependencies
368    /// have already been requested.
369    pub fn are_dependencies_available_for(&self, solvable: SolvableId) -> bool {
370        if self.solvable_to_dependencies.get_copy(&solvable).is_some() {
371            true
372        } else {
373            let solvable_idx = solvable.to_usize();
374            let hint_dependencies_available = self.hint_dependencies_available.borrow();
375            let value = hint_dependencies_available
376                .get(solvable_idx)
377                .as_deref()
378                .copied();
379            value.unwrap_or(false)
380        }
381    }
382}