Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
Fix #1930: "launch" doesn't work with venv on Windows and Python 3.7+
Browse files Browse the repository at this point in the history
For "launch", match processes on parent PID as a fallback for PID, to accommodate launcher stubs like py.exe.
  • Loading branch information
int19h committed Nov 21, 2019
1 parent 6ae5b80 commit bd50356
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 42 deletions.
13 changes: 1 addition & 12 deletions .vscode/launch.json
Expand Up @@ -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.
Expand All @@ -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,
},
]
}
15 changes: 8 additions & 7 deletions src/ptvsd/adapter/ide.py
Expand Up @@ -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):
Expand Down
16 changes: 12 additions & 4 deletions src/ptvsd/adapter/launchers.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
24 changes: 8 additions & 16 deletions src/ptvsd/adapter/servers.py
Expand Up @@ -4,6 +4,7 @@

from __future__ import absolute_import, division, print_function, unicode_literals

import functools
import os
import subprocess
import sys
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -314,7 +311,7 @@ def disconnect(self):
super(Server, self).disconnect()


listen = Connection.listen
listen = functools.partial(Connection.listen, name="Server")


def stop_listening():
Expand All @@ -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.
Expand All @@ -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
Expand Down
9 changes: 6 additions & 3 deletions src/ptvsd/common/sockets.py
Expand Up @@ -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,
)
Expand All @@ -89,7 +92,7 @@ def accept_worker():

log.info(
"Accepted incoming {0} connection from {1}:{2}.",
cls.__name__,
name,
other_host,
other_port,
)
Expand Down

0 comments on commit bd50356

Please sign in to comment.