Skip to content

Commit 6c94249

Browse files
committed
Fix hang with --dist=loadgroup if a crashed worker is replaced
1 parent 8fed345 commit 6c94249

3 files changed

Lines changed: 32 additions & 2 deletions

File tree

changelog/1323.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix hang with `--dist=loadgroup` if a crashed worker is replaced.

src/xdist/scheduler/loadscope.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,10 @@ def remove_node(self, node: WorkerController) -> str | None:
196196
"Unable to identify crashitem on a workload with pending items"
197197
)
198198

199-
# Made uncompleted work unit available again
200-
self.workqueue.update(workload)
199+
# Make uncompleted work units available again
200+
for scope, work_unit in workload.items():
201+
if any(not completed for completed in work_unit.values()):
202+
self.workqueue[scope] = work_unit
201203

202204
for node in self.assigned_work:
203205
self._reschedule(node)
@@ -278,6 +280,12 @@ def _assign_work_unit(self, node: WorkerController) -> None:
278280
for nodeid, completed in work_unit.items()
279281
if not completed
280282
]
283+
if not nodeids_indexes:
284+
# Raise since this is an internal error that may result in a hanging worker
285+
# See #1323
286+
raise RuntimeError(
287+
"Trying to assign a work unit with no pending items to a node"
288+
)
281289

282290
node.send_runtest_some(nodeids_indexes)
283291

testing/acceptance_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,27 @@ def test_b(): pass
974974
]
975975
)
976976

977+
def test_loadgroup_does_not_hang_after_restart(
978+
self, pytester: pytest.Pytester
979+
) -> None:
980+
"""Fix test suite never finishing in case a worker has to be restarted
981+
after having already finished a test (#1323)."""
982+
f = pytester.makepyfile(
983+
"""
984+
import os
985+
def test_a(): pass
986+
def test_b(): os._exit(1)
987+
"""
988+
)
989+
res = pytester.runpytest(f, "-n1", "--dist=loadgroup")
990+
res.stdout.fnmatch_lines(
991+
[
992+
"replacing crashed worker gw*",
993+
"worker*crashed while running*",
994+
"*5 failed*1 passed*",
995+
]
996+
)
997+
977998
def test_max_worker_restart(self, pytester: pytest.Pytester) -> None:
978999
f = pytester.makepyfile(
9791000
"""

0 commit comments

Comments
 (0)