@@ -248,13 +248,24 @@ def get_topological_weights(
248
248
requirement_keys.
249
249
"""
250
250
path : set [str | None ] = set ()
251
- weights : dict [str | None , int ] = {}
251
+ weights : dict [str | None , list [ int ] ] = {}
252
252
253
253
def visit (node : str | None ) -> None :
254
254
if node in path :
255
255
# We hit a cycle, so we'll break it here.
256
256
return
257
257
258
+ # The walk is exponential and for pathologically connected graphs (which
259
+ # are the ones most likely to contain cycles in the first place) it can
260
+ # take until the heat-death of the universe. To counter this we limit
261
+ # the number of attempts to visit (i.e. traverse through) any given
262
+ # node. We choose a value here which gives decent enough coverage for
263
+ # fairly well behaved graphs, and still limits the walk complexity to be
264
+ # linear in nature.
265
+ cur_weights = weights .get (node , [])
266
+ if len (cur_weights ) >= 5 :
267
+ return
268
+
258
269
# Time to visit the children!
259
270
path .add (node )
260
271
for child in graph .iter_children (node ):
@@ -264,14 +275,14 @@ def visit(node: str | None) -> None:
264
275
if node not in requirement_keys :
265
276
return
266
277
267
- last_known_parent_count = weights . get ( node , 0 )
268
- weights [node ] = max ( last_known_parent_count , len ( path ))
278
+ cur_weights . append ( len ( path ) )
279
+ weights [node ] = cur_weights
269
280
270
- # Simplify the graph, pruning leaves that have no dependencies.
271
- # This is needed for large graphs (say over 200 packages) because the
272
- # `visit` function is exponentially slower then , taking minutes.
281
+ # Simplify the graph, pruning leaves that have no dependencies. This is
282
+ # needed for large graphs (say over 200 packages) because the `visit`
283
+ # function is slower for large/densely connected graphs , taking minutes.
273
284
# See https://github.com/pypa/pip/issues/10557
274
- # We will loop until we explicitly break the loop .
285
+ # We repeat the pruning step until we have no more leaves to remove .
275
286
while True :
276
287
leaves = set ()
277
288
for key in graph :
@@ -291,12 +302,13 @@ def visit(node: str | None) -> None:
291
302
for leaf in leaves :
292
303
if leaf not in requirement_keys :
293
304
continue
294
- weights [leaf ] = weight
305
+ weights [leaf ] = [ weight ]
295
306
# Remove the leaves from the graph, making it simpler.
296
307
for leaf in leaves :
297
308
graph .remove (leaf )
298
309
299
- # Visit the remaining graph.
310
+ # Visit the remaining graph, this will only have nodes to handle if the
311
+ # graph had a cycle in it, which the pruning step above could not handle.
300
312
# `None` is guaranteed to be the root node by resolvelib.
301
313
visit (None )
302
314
@@ -305,7 +317,9 @@ def visit(node: str | None) -> None:
305
317
difference = set (weights .keys ()).difference (requirement_keys )
306
318
assert not difference , difference
307
319
308
- return weights
320
+ # Now give back all the weights, choosing the largest ones from what we
321
+ # accumulated.
322
+ return {node : max (wgts ) for (node , wgts ) in weights .items ()}
309
323
310
324
311
325
def _req_set_item_sorter (
0 commit comments