Skip to content

Commit 9ca9f68

Browse files
committed
Server(fix[shutdown]): Close engine on kill-server
why: Non-daemon control-mode threads can hang pytest shutdown when the engine process isn't closed. what: - close the engine in Server.kill cleanup - ensure pytest fixtures close engines on teardown - update control-mode engine tests to assert process termination
1 parent a02f20d commit 9ca9f68

File tree

4 files changed

+27
-13
lines changed

4 files changed

+27
-13
lines changed

conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ def control_sandbox(
118118
finally:
119119
with contextlib.suppress(Exception):
120120
server.kill()
121+
with contextlib.suppress(Exception):
122+
server.engine.close()
121123

122124

123125
@pytest.fixture

src/libtmux/pytest_plugin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ def server(
163163
server = Server(socket_name=socket_name, engine=engine)
164164

165165
def fin() -> None:
166-
server.kill()
166+
with contextlib.suppress(Exception):
167+
server.kill()
168+
with contextlib.suppress(Exception):
169+
server.engine.close()
167170

168171
request.addfinalizer(fin)
169172

src/libtmux/server.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from __future__ import annotations
99

10+
import contextlib
1011
import logging
1112
import os
1213
import pathlib
@@ -440,7 +441,12 @@ def kill(self) -> None:
440441
>>> svr.is_alive()
441442
False
442443
"""
443-
self.cmd("kill-server")
444+
try:
445+
self.cmd("kill-server")
446+
finally:
447+
# Ensure engine resources (e.g., control-mode threads) are released.
448+
with contextlib.suppress(Exception):
449+
self.engine.close()
444450

445451
def kill_session(self, target_session: str | int) -> Server:
446452
"""Kill tmux session.

tests/test_control_mode_engine.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,13 @@ def test_control_mode_engine_basic(tmp_path: pathlib.Path) -> None:
6666
)
6767

6868
# cleanup
69+
proc = engine.process
6970
server.kill()
70-
# Engine process should terminate eventually (ControlModeEngine.close is called
71-
# manually or via weakref/del)
72-
# Server.kill() kills the tmux SERVER. The control mode client process should
73-
# exit as a result.
74-
75-
engine.process.wait(timeout=2)
76-
assert engine.process.poll() is not None
71+
# Server.kill() now closes the engine, which should terminate the client.
72+
assert proc is not None
73+
proc.wait(timeout=2)
74+
assert proc.poll() is not None
75+
assert engine.process is None
7776

7877

7978
def test_control_mode_timeout(monkeypatch: pytest.MonkeyPatch) -> None:
@@ -188,9 +187,11 @@ def test_control_mode_custom_session_name(tmp_path: pathlib.Path) -> None:
188187
assert len(all_sessions) == 2
189188

190189
# Cleanup
190+
proc = engine.process
191191
server.kill()
192-
assert engine.process is not None
193-
engine.process.wait(timeout=2)
192+
assert proc is not None
193+
proc.wait(timeout=2)
194+
assert engine.process is None
194195

195196

196197
def test_control_mode_control_session_existing(tmp_path: pathlib.Path) -> None:
@@ -221,9 +222,11 @@ def test_control_mode_control_session_existing(tmp_path: pathlib.Path) -> None:
221222
assert len(all_sessions) == 1 # Only shared_session
222223

223224
# Cleanup
225+
proc = control_engine.process
224226
server2.kill()
225-
assert control_engine.process is not None
226-
control_engine.process.wait(timeout=2)
227+
assert proc is not None
228+
proc.wait(timeout=2)
229+
assert control_engine.process is None
227230

228231

229232
class RestartFixture(t.NamedTuple):

0 commit comments

Comments
 (0)