Repository navigation
loadscope: forget a dead worker's collection so a still-collecting worker isn't mistaken for done - #1364
Conversation
|
Is this related to / dup of #1363 ? |
|
No — different bug, different line, and they can both be wrong at once. Same file, which is why they look alike. #1363 / #1313 is about This PR / #1362 is about def collection_is_completed(self) -> bool:
return len(self.registered_collections) >= self.numnodescounts entries rather than live workers, the stale entry makes the collection look complete while a later worker is still collecting. The two diffs do not touch the same function — #1363 edits Happy to rebase and re-run if that helps. |
|
Not a duplicate — different bug, different line, and they don't overlap. #1363 (and #1328, which you preferred) is about #1313: a crashed worker's completed work units get requeued, a replacement is handed an empty unit, and the session hangs. It changes This one is #1362: So a session could hit either one independently: #1328 doesn't touch |
|
Thanks for this. The stale entry is a real fault, and dropping it fixes the
Reproduced against this branch (e6ef607) and against master with a real import types
from xdist.scheduler.loadscope import LoadScopeScheduling
class Node:
def __init__(self, name):
self.gateway = types.SimpleNamespace(id=name)
self.shutting_down = False
self.sent = []
def send_runtest_some(self, indices):
self.sent.extend(indices)
def shutdown(self):
self.shutting_down = True
config = types.SimpleNamespace(
getvalue=lambda name: ["3*popen"],
option=types.SimpleNamespace(loadscopereorder=False),
)
collection = [f"test_{m}.py::test_{i}" for m in "abcdef" for i in range(3)]
sched = LoadScopeScheduling(config)
a, b, c = Node("gw0"), Node("gw1"), Node("gw2")
for node in (a, b, c):
sched.add_node(node)
for node in (a, b, c):
sched.add_node_collection(node, collection)
if sched.collection_is_completed:
sched.schedule()
sched.remove_node(a) # gw0 crashes
sched.add_node(Node("gw3")) # its replacement starts collecting
sched.remove_node(b) # gw1 crashes: KeyError, on this branch and on masterWhat closes both routes for us is the check from #1299: For context, we hit this in a real run when two workers died in the same second. The |
remove_node reschedules every node in assigned_work, which includes a replacement worker that is still collecting, and _assign_work_unit then raised KeyError on registered_collections for it. The guard is the same one proposed in pytest-dev#1299.
|
@davidheff thanks. That's a real second route, and your repro made it quick to pin down. I added the guard here: New test, I also updated the changelog fragment to cover both routes. |
Fixes #1362.
Problem
LoadScopeScheduling.remove_node()pops the dead worker fromassigned_workbut never removes its entry fromregistered_collections. Sincecollection_is_completedislen(registered_collections) >= numnodes, the stale entry keeps the collection looking "complete" while a later worker is still collecting.schedule()then runs and_assign_work_unit()raisesKeyErrorindexingregistered_collectionsfor the worker that never registered — crashing the run withINTERNALERROR. This affects both--dist=loadscopeand--dist=loadfile(the latter subclassesLoadScopeScheduling).Fix
Drop the node from
registered_collectionsinremove_node(), mirroring theassigned_work.pop(node)already there. A replacement worker then re-registers normally, andcollection_is_completedcorrectly waits for every live worker.This is safe for the shutdown path too: normal
worker_workerfinishedremovals only happen aftertriggershutdown, and on a crash the replacement worker re-registers its collection.Test
Added
TestLoadScopeScheduling::test_remove_node_forgets_dead_worker_collection, which reproduces the scenario withMockNodes (3 expected workers; one dies mid-collection while another is still collecting). It fails onmain(registered_collectionsstill holds the dead node,collection_is_completedflips true early) and passes with the fix. Fulltesting/test_dsession.pyis green;ruff check/formatclean. Addedchangelog/1362.bugfix.rst.