Skip to content

Commit 92d229b

Browse files
committed
Speed up backtracking by skipping unrelated states.
The current backtracking logic assumes that the last state in the stack is the state that caused the incompatibility. This is not always the case. For example, given three requirements A, B and C, with dependencies A1, B1 and C1, where A1 and B1 are incompatible. The requirements are processed one after the other, so the last state is related to C, while the incompatibility is caused by B. The current behavior causes significant slowdowns in case there are many candidates for B and C, as all their combination are evaluated before a compatible version of B can be found. The new behavior discards a state if the packages that cause the incompatibility are not found among the direct dependencies of the candidate in the current state. In our example, this causes the state related to C to be dropped without evaluating any of its candidates, until a compatible candidate of B is found. Signed-off-by: Stefano Bennati stefano.bennati@here.com
1 parent 7b66e2d commit 92d229b

File tree

1 file changed

+19
-8
lines changed

1 file changed

+19
-8
lines changed

src/resolvelib/resolvers.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,22 +283,33 @@ def _backtrack(self):
283283
284284
Each iteration of the loop will:
285285
286-
1. Discard Z.
287-
2. Discard Y but remember its incompatibility information gathered
286+
1. Identify Z. The incompatibility is not necessarily caused by the latest state.
287+
For example, given three requirements A, B and C, with dependencies
288+
A1, B1 and C1, where A1 and B1 are incompatible:
289+
the last state might be related to C, so we want to discard the previous state.
290+
2. Discard Z.
291+
3. Discard Y but remember its incompatibility information gathered
288292
previously, and the failure we're dealing with right now.
289-
3. Push a new state Y' based on X, and apply the incompatibility
293+
4. Push a new state Y' based on X, and apply the incompatibility
290294
information from Y to Y'.
291-
4a. If this causes Y' to conflict, we need to backtrack again. Make Y'
295+
5a. If this causes Y' to conflict, we need to backtrack again. Make Y'
292296
the new Z and go back to step 2.
293-
4b. If the incompatibilities apply cleanly, end backtracking.
297+
5b. If the incompatibilities apply cleanly, end backtracking.
294298
"""
295299
while len(self._states) >= 3:
296300
# Remove the state that triggered backtracking.
297301
del self._states[-1]
298302

299-
# Retrieve the last candidate pin and known incompatibilities.
300-
broken_state = self._states.pop()
301-
name, candidate = broken_state.mapping.popitem()
303+
# Ensure to backtrack to a state that caused the incompatibility
304+
incompatible_state = False
305+
while not incompatible_state:
306+
# Retrieve the last candidate pin and known incompatibilities.
307+
broken_state = self._states.pop()
308+
name, candidate = broken_state.mapping.popitem()
309+
current_dependencies = set([d.name for d in self._p.get_dependencies(candidate)])
310+
incompatible_deps = set([c.parent.name for c in causes]) # Parent should never be null
311+
incompatible_state = current_dependencies.intersection(incompatible_deps) != set()
312+
302313
incompatibilities_from_broken = [
303314
(k, list(v.incompatibilities))
304315
for k, v in broken_state.criteria.items()

0 commit comments

Comments
 (0)