Skip to content

Commit

Permalink
Fix microsoft#1930: "launch" doesn't work with venv on Windows and Py…
Browse files Browse the repository at this point in the history
…thon 3.7+

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 61827df
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 20 deletions.
15 changes: 8 additions & 7 deletions src/ptvsd/adapter/ide.py
Original file line number Diff line number Diff line change
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
7 changes: 6 additions & 1 deletion src/ptvsd/adapter/launchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ def spawn_launcher():
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.
pred = lambda conn: conn.pid == session.pid or conn.ppid == session.pid
conn = servers.wait_for_connection(session, pred, timeout=10)
if conn is None:
raise start_request.cant_handle(
"{0} timed out waiting for debuggee to spawn", session
Expand Down
14 changes: 5 additions & 9 deletions src/ptvsd/adapter/servers.py
Original file line number Diff line number Diff line change
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 @@ -314,7 +315,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 +330,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 +353,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
Original file line number Diff line number Diff line change
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 61827df

Please sign in to comment.