From 0d966c7a7d62f41b5d200fd7fd6e74cd2557dfa2 Mon Sep 17 00:00:00 2001 From: Tele Oyekunle Date: Mon, 30 Mar 2026 21:20:33 +0000 Subject: [PATCH] Fix _sock_recv infinite loop when StatusDB TCP connection drops When using --reruns with pytest-xdist, every test makes two blocking TCP calls to the StatusDB server (get_test_failures and set_test_reruns in pytest_runtest_protocol). If the server-side connection drops, _sock_recv enters an infinite loop because recv(1) returns b'' (empty bytes) on a closed socket, but the code only checks for the newline delimiter: while True: b = conn.recv(1) if b == self.delim: # b'' != b'\n' -> never breaks break buf += b This causes xdist workers to hang indefinitely at ~90% CPU, appearing stuck on a test that never completes. The hang persists until the process is manually killed. The fix adds a check for empty bytes from recv(1) and raises ConnectionError, which surfaces as an INTERNALERROR that xdist handles by replacing the worker. --- CHANGES.rst | 4 ++++ src/pytest_rerunfailures.py | 2 ++ tests/test_pytest_rerunfailures.py | 21 ++++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8638e1c..43dd973 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,10 @@ Changelog - Fix missing teardown for session and module scoped fixtures when fixture teardown fails. Fixes `#314 `_. +- Fix ``_sock_recv`` infinite loop when the StatusDB TCP connection drops. + ``recv(1)`` returning empty bytes (closed connection) was not handled, + causing workers to spin at 100% CPU indefinitely during xdist runs. + 16.1 (2025-10-10) ----------------- diff --git a/src/pytest_rerunfailures.py b/src/pytest_rerunfailures.py index 63223ae..9e0ffb0 100644 --- a/src/pytest_rerunfailures.py +++ b/src/pytest_rerunfailures.py @@ -436,6 +436,8 @@ def _sock_recv(self, conn) -> str: buf = b"" while True: b = conn.recv(1) + if not b: + raise ConnectionError("StatusDB connection closed unexpectedly") if b == self.delim: break buf += b diff --git a/tests/test_pytest_rerunfailures.py b/tests/test_pytest_rerunfailures.py index 7156bc9..088aea5 100644 --- a/tests/test_pytest_rerunfailures.py +++ b/tests/test_pytest_rerunfailures.py @@ -1,10 +1,11 @@ import random +import socket import time from unittest import mock import pytest -from pytest_rerunfailures import HAS_PYTEST_HANDLECRASHITEM +from pytest_rerunfailures import HAS_PYTEST_HANDLECRASHITEM, SocketDB pytest_plugins = "pytester" @@ -1407,3 +1408,21 @@ def test_fail(): result = testdir.runpytest("--force-reruns", "3") assert_outcomes(result, passed=0, failed=1, rerun=3) + + +def test_sock_recv_raises_on_closed_connection(): + """_sock_recv should raise ConnectionError when recv returns empty bytes + (connection closed), not loop forever. + + Previously, _sock_recv had no check for empty bytes from recv(1), causing + an infinite CPU-spinning loop when the server-side connection dropped. + This manifested as indefinite hangs during xdist test runs. + """ + s1, s2 = socket.socketpair() + s2.close() # Close one end — recv on s1 will return b"" + + db = SocketDB() + with pytest.raises(ConnectionError, match="closed unexpectedly"): + db._sock_recv(s1) + + s1.close()