Skip to content

Commit f3b7cc8

Browse files
committed
[lldb/test] Add ability to terminate connection from a gdb-client handler
We were using the client socket close as a way to terminate the handler thread. But this kind of concurrent access to the same socket is not safe. It also complicates running the handler without a dedicated thread (next patch). Instead, here I add an explicit way for a packet handler to request termination. Waiting for lldb to terminate the connection would almost be sufficient, but in the pty test we want to keep the pty open so we can examine its state. Ability to disconnect at an arbitrary point may be useful for testing other aspects of lldb functionality as well. The way this works is that now each packet handler can optionally return a list of responses (instead of just one). One of those responses (it only makes sense for it to be the last one) can be a special RESPONSE_DISCONNECT object, which triggers a disconnection (via a new TerminateConnectionException). As the mock server now cleans up the connection whenever it disconnects, the pty test needs to explicitly dup(2) the descriptors in order to inspect the post-disconnect state. Differential Revision: https://reviews.llvm.org/D114156
1 parent 2800058 commit f3b7cc8

File tree

2 files changed

+32
-20
lines changed

2 files changed

+32
-20
lines changed

lldb/packages/Python/lldbsuite/test/gdbclientutils.py

+27-19
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class MockGDBServerResponder:
8686

8787
registerCount = 40
8888
packetLog = None
89+
class RESPONSE_DISCONNECT: pass
8990

9091
def __init__(self):
9192
self.packetLog = []
@@ -327,7 +328,7 @@ def qRegisterInfo(self, num):
327328
return ""
328329

329330
def k(self):
330-
return ""
331+
return ["W01", self.RESPONSE_DISCONNECT]
331332

332333
"""
333334
Raised when we receive a packet for which there is no default action.
@@ -492,31 +493,31 @@ def _run(self):
492493
self._receivedData = ""
493494
self._receivedDataOffset = 0
494495
data = None
495-
while True:
496-
try:
496+
try:
497+
while True:
497498
data = seven.bitcast_to_string(self._socket.recv())
498499
if data is None or len(data) == 0:
499500
break
500501
self._receive(data)
501-
except Exception as e:
502-
print("An exception happened when receiving the response from the gdb server. Closing the client...")
503-
traceback.print_exc()
504-
self._socket.close_connection()
505-
break
502+
except self.TerminateConnectionException:
503+
pass
504+
except Exception as e:
505+
print("An exception happened when receiving the response from the gdb server. Closing the client...")
506+
traceback.print_exc()
507+
finally:
508+
self._socket.close_connection()
509+
self._socket.close_server()
506510

507511
def _receive(self, data):
508512
"""
509513
Collects data, parses and responds to as many packets as exist.
510514
Any leftover data is kept for parsing the next time around.
511515
"""
512516
self._receivedData += data
513-
try:
517+
packet = self._parsePacket()
518+
while packet is not None:
519+
self._handlePacket(packet)
514520
packet = self._parsePacket()
515-
while packet is not None:
516-
self._handlePacket(packet)
517-
packet = self._parsePacket()
518-
except self.InvalidPacketException:
519-
self._socket.close_connection()
520521

521522
def _parsePacket(self):
522523
"""
@@ -583,6 +584,9 @@ def _parsePacket(self):
583584
self._receivedDataOffset = 0
584585
return packet
585586

587+
def _sendPacket(self, packet):
588+
self._socket.sendall(seven.bitcast_to_bytes(frame_packet(packet)))
589+
586590
def _handlePacket(self, packet):
587591
if packet is self.PACKET_ACK:
588592
# Ignore ACKs from the client. For the future, we can consider
@@ -600,14 +604,18 @@ def _handlePacket(self, packet):
600604
elif self.responder is not None:
601605
# Delegate everything else to our responder
602606
response = self.responder.respond(packet)
603-
# Handle packet framing since we don't want to bother tests with it.
604-
if response is not None:
605-
framed = frame_packet(response)
606-
self._socket.sendall(seven.bitcast_to_bytes(framed))
607+
if not isinstance(response, list):
608+
response = [response]
609+
for part in response:
610+
if part is MockGDBServerResponder.RESPONSE_DISCONNECT:
611+
raise self.TerminateConnectionException()
612+
self._sendPacket(part)
607613

608614
PACKET_ACK = object()
609615
PACKET_INTERRUPT = object()
610616

611-
class InvalidPacketException(Exception):
617+
class TerminateConnectionException(Exception):
612618
pass
613619

620+
class InvalidPacketException(Exception):
621+
pass

lldb/test/API/functionalities/gdb_remote_client/TestPty.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ class TestPty(GDBRemoteTestBase):
1212

1313
def get_term_attrs(self):
1414
import termios
15-
return termios.tcgetattr(self.server._socket._secondary)
15+
return termios.tcgetattr(self._secondary_socket)
1616

1717
def setUp(self):
1818
super().setUp()
19+
# Duplicate the pty descriptors so we can inspect the pty state after
20+
# they are closed
21+
self._primary_socket = os.dup(self.server._socket._primary.name)
22+
self._secondary_socket = os.dup(self.server._socket._secondary.name)
1923
self.orig_attr = self.get_term_attrs()
2024

2125
def assert_raw_mode(self, current_attr):

0 commit comments

Comments
 (0)