[lldb-dap][test] Avoid a hang on a test failure#209988
Conversation
Tests in 'TestDAP_server.py' create 'DebugAdapterServer', passing a connection string. 'DebugAdapterServer.__init__()' creates a socket and passes the associated input and output streams to its parent class, 'DebugCommunication'. 'DAPTestCaseBase.launch()', which is called from 'TestDAP_server.run_debug_session()', adds a teardown hook that eventually calls 'DebugCommunication.terminate()'. If at the moment this hook executes the socket is not closed yet, it waits indefinitely for the receiving thread to join. In the normal case, when a test passes, the connection is closed by calling 'self.dap_server.request_disconnect()' at the end of 'TestDAP_server.run_debug_session()'. If a test fails, the cleanup hook is called while the connection is still active, which results in a hang. This can be reproduced by removing the call to 'request_disconnect()' or by changing the condition in the 'assertEqual()' on the previous line. The fix passes the opened socket to 'DebugCommunication' and closes the socket explicitly in 'DebugCommunication.terminate()'.
|
@llvm/pr-subscribers-lldb Author: Igor Kudrin (igorkudrin) ChangesTests in In the normal case, when a test passes, the connection is closed by calling The fix passes the opened socket to 'DebugCommunication' and closes the socket explicitly in 'DebugCommunication.terminate()'. Full diff: https://github.com/llvm/llvm-project/pull/209988.diff 1 Files Affected:
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index c8acf67b23b99..8f710154c5da3 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -205,11 +205,13 @@ def __init__(
send: BinaryIO,
init_commands: Optional[List[str]] = None,
spawn_helper: Optional[SpawnHelperCallback] = None,
+ socket: Optional[socket.socket] = None,
):
self._log = Log()
self.send = send
self.recv = recv
self.spawn_helper = spawn_helper
+ self.socket = socket
# Packets that have been received and processed but have not yet been
# requested by a test case.
@@ -1643,6 +1645,8 @@ def request_custom(self, command: str, arguments: Optional[dict[str, Any]] = Non
return self._send_recv(command_dict)
def terminate(self):
+ if self.socket:
+ self.socket.shutdown(socket.SHUT_RDWR)
self.send.close()
if self._recv_thread.is_alive():
self._recv_thread.join()
@@ -1706,6 +1710,7 @@ def __init__(
s.makefile("wb"),
init_commands,
spawn_helper,
+ socket=s,
)
self.connection = connection
else:
|
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
Tests in
TestDAP_server.pycreateDebugAdapterServer, passing a connection string.DebugAdapterServer.__init__()creates a socket and passes the associated input and output streams to its parent class,DebugCommunication.DAPTestCaseBase.launch(), which is called fromTestDAP_server.run_debug_session(), adds a teardown hook that eventually callsDebugCommunication.terminate(). If the socket is not closed when this hook executes, it waits indefinitely for the receiving thread to join.In the normal case, when a test passes, the connection is closed by calling
self.dap_server.request_disconnect()at the end ofTestDAP_server.run_debug_session(). If a test fails, the cleanup hook is called while the connection is still active, which results in a hang. This can be reproduced by removing the call torequest_disconnect()or by changing the condition in theassertEqual()on the previous line.The fix passes the opened socket to 'DebugCommunication' and closes the socket explicitly in 'DebugCommunication.terminate()'.