diff --git a/.vscode/launch.json b/.vscode/launch.json index b8023f6f5..bd5d5f74f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "consoleTitle": "ptvsd.adapter", "program": "${workspaceFolder}/src/ptvsd/adapter", "args": ["--port", "8765", "--log-stderr"], - "customDebugger": true, + "noDebug": true, }, // For these, ptvsd.adapter must be started first via the above configuration. @@ -27,24 +27,13 @@ "consoleTitle": "ptvsd.server", //"program": "${file}", "program": "${workspaceFolder}/tests/test_data/testpkgs/pkg1/__main__.py", - //"ptvsdArgs": ["--log-stderr"], }, { - //"debugServer": 8765, "name": "Attach [debugServer]", "type": "python", "request": "attach", "host": "localhost", "port": 5678, }, - { - //"debugServer": 8765, - "name": "Attach Child Process [debugServer]", - "type": "python", - "request": "attach", - "host": "localhost", - "port": 5678, - "subProcessId": 00000, - }, ] } \ No newline at end of file diff --git a/src/ptvsd/adapter/ide.py b/src/ptvsd/adapter/ide.py index 3efda2ee9..d3c5f3f70 100644 --- a/src/ptvsd/adapter/ide.py +++ b/src/ptvsd/adapter/ide.py @@ -311,31 +311,32 @@ def attach_request(self, request): ptvsd_args = request("ptvsdArgs", json.array(unicode)) servers.inject(pid, ptvsd_args) timeout = 10 + pred = lambda conn: conn.pid == pid else: if sub_pid == (): - pid = any + pred = lambda conn: True timeout = None if request("waitForAttach", False) else 10 else: - pid = sub_pid + pred = lambda conn: conn.pid == sub_pid timeout = 0 - conn = servers.wait_for_connection(pid, timeout) + conn = servers.wait_for_connection(self.session, pred, timeout) if conn is None: raise request.cant_handle( ( - "Timed out waiting for injected debug server to connect" + "Timed out waiting for debug server to connect." if timeout else "There is no debug server connected to this adapter." - if pid is any + if sub_pid == () else 'No known subprocess with "subProcessId":{0}' ), - pid, + sub_pid, ) try: conn.attach_to_session(self.session) except ValueError: - request.cant_handle("Debuggee with PID={0} is already being debugged.", pid) + request.cant_handle("{0} is already being debugged.", conn) @message_handler def configurationDone_request(self, request): diff --git a/src/ptvsd/adapter/launchers.py b/src/ptvsd/adapter/launchers.py index 20b8ee902..60e0527e9 100644 --- a/src/ptvsd/adapter/launchers.py +++ b/src/ptvsd/adapter/launchers.py @@ -34,8 +34,6 @@ def __init__(self, session, stream): @message_handler def process_event(self, event): self.pid = event("systemProcessId", int) - assert self.session.pid is None - self.session.pid = self.pid self.ide.propagate_after_start(event) @message_handler @@ -115,14 +113,24 @@ def spawn_launcher(): arguments["port"] = port spawn_launcher() - if not session.wait_for(lambda: session.pid is not None, timeout=5): + if not session.wait_for( + lambda: session.launcher is not None and session.launcher.pid is not None, + timeout=5, + ): raise start_request.cant_handle( '{0} timed out waiting for "process" event from {1}', session, session.launcher, ) - conn = servers.wait_for_connection(session.pid, timeout=10) + # Python can be started via a stub - e.g. py.exe on Windows, which doubles + # as python.exe in virtual environments. In this case, the PID of the process + # that connects to us won't match the PID of the process that we spawned, but + # will have the latter as its parent. + pid = session.launcher.pid + conn = servers.wait_for_connection( + session, (lambda conn: pid in (conn.pid, conn.ppid)), timeout=10 + ) if conn is None: raise start_request.cant_handle( "{0} timed out waiting for debuggee to spawn", session diff --git a/src/ptvsd/adapter/servers.py b/src/ptvsd/adapter/servers.py index 791f6fcb9..25e3c9a8b 100644 --- a/src/ptvsd/adapter/servers.py +++ b/src/ptvsd/adapter/servers.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals +import functools import os import subprocess import sys @@ -206,18 +207,14 @@ def __init__(self, session, connection): self.connection = connection - if self.launcher: - assert self.session.pid is not None - else: - assert self.session.pid is None - if self.session.pid is not None and self.session.pid != self.pid: + assert self.session.pid is None + if self.session.launcher and self.session.launcher.pid != self.pid: log.warning( "Launcher reported PID={0}, but server reported PID={1}", self.session.pid, self.pid, ) - else: - self.session.pid = self.pid + self.session.pid = self.pid session.server = self @@ -314,7 +311,7 @@ def disconnect(self): super(Server, self).disconnect() -listen = Connection.listen +listen = functools.partial(Connection.listen, name="Server") def stop_listening(): @@ -329,7 +326,7 @@ def connections(): return list(_connections) -def wait_for_connection(pid=any, timeout=None): +def wait_for_connection(session, predicate, timeout=None): """Waits until there is a server with the specified PID connected to this adapter, and returns the corresponding Connection. @@ -352,16 +349,11 @@ def wait_for_timeout(): thread.start() if timeout != 0: - log.info( - "Waiting for connection from debug server..." - if pid is any - else "Waiting for connection from debug server with PID={0}...", - pid, - ) + log.info("{0} waiting for connection from debug server...", session) while True: with _lock: _connections_changed.clear() - conns = (conn for conn in _connections if pid is any or conn.pid == pid) + conns = (conn for conn in _connections if predicate(conn)) conn = next(conns, None) if conn is not None or wait_for_timeout.timed_out: return conn diff --git a/src/ptvsd/common/sockets.py b/src/ptvsd/common/sockets.py index cf6c2a00a..da1836f53 100644 --- a/src/ptvsd/common/sockets.py +++ b/src/ptvsd/common/sockets.py @@ -64,17 +64,20 @@ class ClientConnection(object): """ @classmethod - def listen(cls, host=None, port=0, timeout=None): + def listen(cls, host=None, port=0, timeout=None, name=None): """Accepts TCP connections on the specified host and port, and creates a new instance of this class wrapping every accepted socket. """ + if name is None: + name = cls.__name__ + assert cls.listener is None cls.listener = create_server(host, port, timeout) host, port = cls.listener.getsockname() log.info( "Waiting for incoming {0} connections on {1}:{2}...", - cls.__name__, + name, host, port, ) @@ -89,7 +92,7 @@ def accept_worker(): log.info( "Accepted incoming {0} connection from {1}:{2}.", - cls.__name__, + name, other_host, other_port, )