|  | 
| 1 | 1 | import lldb | 
|  | 2 | +import os | 
| 2 | 3 | import re | 
|  | 4 | +import socket | 
|  | 5 | +import time | 
| 3 | 6 | 
 | 
|  | 7 | +from contextlib import closing | 
| 4 | 8 | from lldbsuite.test.decorators import * | 
| 5 | 9 | from lldbsuite.test.lldbtest import * | 
| 6 | 10 | from lldbsuite.test.lldbpexpect import PExpectTest | 
|  | 11 | +from lldbgdbserverutils import get_lldb_server_exe | 
| 7 | 12 | 
 | 
| 8 | 13 | 
 | 
| 9 | 14 | # PExpect uses many timeouts internally and doesn't play well | 
| @@ -115,3 +120,82 @@ def test_resize(self): | 
| 115 | 120 |         # Check for the escape code to resize the scroll window. | 
| 116 | 121 |         self.child.expect(re.escape("\x1b[1;19r")) | 
| 117 | 122 |         self.child.expect("(lldb)") | 
|  | 123 | + | 
|  | 124 | +    @skipIfRemote | 
|  | 125 | +    @skipIfWindows | 
|  | 126 | +    @skipIfDarwin | 
|  | 127 | +    @add_test_categories(["lldb-server"]) | 
|  | 128 | +    def test_modulelist_deadlock(self): | 
|  | 129 | +        """Regression test for a deadlock that occurs when the status line is enabled before connecting | 
|  | 130 | +        to a gdb-remote server. | 
|  | 131 | +        """ | 
|  | 132 | +        if get_lldb_server_exe() is None: | 
|  | 133 | +            self.skipTest("lldb-server not found") | 
|  | 134 | + | 
|  | 135 | +        MAX_RETRY_ATTEMPTS = 10 | 
|  | 136 | +        DELAY = 0.25 | 
|  | 137 | + | 
|  | 138 | +        def _find_free_port(host): | 
|  | 139 | +            for attempt in range(MAX_RETRY_ATTEMPTS): | 
|  | 140 | +                try: | 
|  | 141 | +                    family, type, proto, _, _ = socket.getaddrinfo( | 
|  | 142 | +                        host, 0, proto=socket.IPPROTO_TCP | 
|  | 143 | +                    )[0] | 
|  | 144 | +                    with closing(socket.socket(family, type, proto)) as sock: | 
|  | 145 | +                        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
|  | 146 | +                        sock.bind((host, 0)) | 
|  | 147 | +                        return sock.getsockname() | 
|  | 148 | +                except OSError: | 
|  | 149 | +                    time.sleep(DELAY * 2**attempt)  # Exponential backoff | 
|  | 150 | +            raise RuntimeError( | 
|  | 151 | +                "Could not find a free port on {} after {} attempts.".format( | 
|  | 152 | +                    host, MAX_RETRY_ATTEMPTS | 
|  | 153 | +                ) | 
|  | 154 | +            ) | 
|  | 155 | + | 
|  | 156 | +        def _wait_for_server_ready_in_log(log_file_path, ready_message): | 
|  | 157 | +            """Check log file for server ready message with retry logic""" | 
|  | 158 | +            for attempt in range(MAX_RETRY_ATTEMPTS): | 
|  | 159 | +                if os.path.exists(log_file_path): | 
|  | 160 | +                    with open(log_file_path, "r") as f: | 
|  | 161 | +                        if ready_message in f.read(): | 
|  | 162 | +                            return | 
|  | 163 | +                time.sleep(pow(2, attempt) * DELAY) | 
|  | 164 | +            raise RuntimeError( | 
|  | 165 | +                "Server not ready after {} attempts.".format(MAX_RETRY_ATTEMPTS) | 
|  | 166 | +            ) | 
|  | 167 | + | 
|  | 168 | +        self.build() | 
|  | 169 | +        exe_path = self.getBuildArtifact("a.out") | 
|  | 170 | +        server_log_file = self.getLogBasenameForCurrentTest() + "-lldbserver.log" | 
|  | 171 | +        self.addTearDownHook( | 
|  | 172 | +            lambda: os.path.exists(server_log_file) and os.remove(server_log_file) | 
|  | 173 | +        ) | 
|  | 174 | + | 
|  | 175 | +        # Find a free port for the server | 
|  | 176 | +        addr = _find_free_port("localhost") | 
|  | 177 | +        connect_address = "[{}]:{}".format(*addr) | 
|  | 178 | +        commandline_args = [ | 
|  | 179 | +            "gdbserver", | 
|  | 180 | +            connect_address, | 
|  | 181 | +            exe_path, | 
|  | 182 | +            "--log-file={}".format(server_log_file), | 
|  | 183 | +            "--log-channels=lldb conn", | 
|  | 184 | +        ] | 
|  | 185 | + | 
|  | 186 | +        server_proc = self.spawnSubprocess( | 
|  | 187 | +            get_lldb_server_exe(), commandline_args, install_remote=False | 
|  | 188 | +        ) | 
|  | 189 | +        self.assertIsNotNone(server_proc) | 
|  | 190 | + | 
|  | 191 | +        # Wait for server to be ready by checking log file. | 
|  | 192 | +        server_ready_message = "Listen to {}".format(connect_address) | 
|  | 193 | +        _wait_for_server_ready_in_log(server_log_file, server_ready_message) | 
|  | 194 | + | 
|  | 195 | +        # Launch LLDB client and connect to lldb-server with statusline enabled | 
|  | 196 | +        self.launch(timeout=self.TIMEOUT) | 
|  | 197 | +        self.resize() | 
|  | 198 | +        self.expect("settings set show-statusline true", ["no target"]) | 
|  | 199 | +        self.expect( | 
|  | 200 | +            f"gdb-remote {connect_address}", [b"a.out \xe2\x94\x82 signal SIGSTOP"] | 
|  | 201 | +        ) | 
0 commit comments