@@ -79,8 +79,31 @@ def open_connection(*, broker_kwargs=None, **parameter_overrides):
7979
8080@pytest .fixture
8181def broker_farm (monkeypatch ):
82- """Return a :class:`BrokerFarm` answering every dial, redials included."""
82+ """Return a :class:`BrokerFarm` answering every dial, redials included.
83+
84+ Every ``Connection`` built while this fixture is active is tracked and
85+ force-closed in teardown, even if the test body never reaches its own
86+ ``connection.close()`` (e.g. because an earlier assertion failed). A test
87+ that skips that call would otherwise leave a live recovery thread behind;
88+ since ``_connect_socket`` is a module-level hook each test's fixture
89+ re-monkeypatches independently, that orphaned thread's next redial would
90+ call whichever ``_connect_socket`` a *later*, unrelated test has bound —
91+ hijacking that test's fresh socket mid-handshake. The failure is a timing
92+ race (rare locally, common on slower CI runners), so closing every
93+ connection unconditionally is what actually closes the window.
94+ """
8395 farm = BrokerFarm ()
96+ connections : list [Connection ] = []
97+ original_init = Connection .__init__
98+
99+ def tracked_init (self , * args , ** kwargs ):
100+ connections .append (self )
101+ original_init (self , * args , ** kwargs )
102+
103+ monkeypatch .setattr (Connection , "__init__" , tracked_init )
84104 monkeypatch .setattr (connection_module , "_connect_socket" , farm .dial )
85105 yield farm
106+ for connection in connections :
107+ with contextlib .suppress (Exception ): # teardown must never mask a test failure
108+ connection .close ()
86109 farm .close ()
0 commit comments