Fix #1810: tests using attach_by_pid fail on Linux and macOS

Add time.sleep() to attach_by_pid spinning loop to give the injected code a better chance to execute.

Read and log injector output to prevent it from blocking on prints.

Improve code injection logging.

Fix race between socket.accept() backchannel listener thread, and debug.Session.close().
This commit is contained in:
Pavel Minaev 2019-11-12 18:40:06 -08:00 committed by Pavel Minaev
parent 672c310905
commit 646981e4af
4 changed files with 41 additions and 18 deletions

View file

@ -86,16 +86,12 @@ if 'ptvsd' not in sys.modules:
inject_ptvsd = fmt(inject_ptvsd, ptvsd_dir=ptvsd_dir)
try:
self.channel.request(
"evaluate", {"expression": inject_ptvsd}
)
self.channel.request("evaluate", {"expression": inject_ptvsd})
except messaging.MessageHandlingError:
# Failure to inject is not a fatal error - such a subprocess can
# still be debugged, it just won't support "import ptvsd" in user
# code - so don't terminate the session.
log.exception(
"Failed to inject ptvsd into {0}:", self, level="warning"
)
log.exception("Failed to inject ptvsd into {0}:", self, level="warning")
with _lock:
if any(conn.pid == self.pid for conn in _connections):
@ -343,7 +339,7 @@ def wait_for_connection(pid=any, timeout=None):
wait_for_timeout.timed_out = timeout == 0
if timeout:
thread = threading.Thread(
target=wait_for_timeout, name="server.wait_for_connection() timeout"
target=wait_for_timeout, name="servers.wait_for_connection() timeout"
)
thread.daemon = True
thread.start()
@ -404,15 +400,32 @@ def inject(pid, ptvsd_args):
log.info("Spawning attach-to-PID debugger injector: {0!r}", cmdline)
try:
subprocess.Popen(
injector = subprocess.Popen(
cmdline,
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
except Exception as exc:
log.exception("Failed to inject debug server into process with PID={0}", pid)
raise messaging.MessageHandlingError(
"Failed to inject debug server into process with PID={0}: {1}", pid, exc
)
# We need to capture the output of the injector - otherwise it can get blocked
# on a write() syscall when it tries to print something.
def capture_output():
while True:
line = injector.stdout.readline()
if not line:
break
log.info("Injector[PID={0}] output:\n{1}", pid, line.rstrip())
log.info("Injector[PID={0}] exited.", pid)
thread = threading.Thread(
target=capture_output, name=fmt("Injector[PID={0}] output", pid)
)
thread.daemon = True
thread.start()

View file

@ -267,7 +267,7 @@ def run_code():
def attach_to_pid():
log.info("Attaching to process with ID {0}", options.target)
log.info("Attaching to process with PID={0}", options.target)
pid = options.target
host = options.host
@ -324,7 +324,7 @@ attach_pid_injected.attach(port={port}, host=host, client={client}, log_dir=log_
import add_code_to_python_process # noqa
show_debug_info_on_target_process = 0 # hard-coded (1 to debug)
log.info("Code injector begin")
log.info("Injecting code into process with PID={0} ...", pid)
add_code_to_python_process.run_python_code(
pid,
python_code,
@ -332,8 +332,8 @@ attach_pid_injected.attach(port={port}, host=host, client={client}, log_dir=log_
show_debug_info=show_debug_info_on_target_process,
)
except Exception:
raise log.exception()
log.info("Code injector exiting")
raise log.exception("Code injection into PID={0} failed:", pid)
log.info("Code injection into PID={0} completed.", pid)
def main():

View file

@ -46,6 +46,11 @@ class BackChannel(object):
sock, _ = server_socket.accept()
except socket.timeout:
raise log.exception("Timed out waiting for {0} to connect", self)
except Exception:
if self._server_socket is None:
return
else:
raise
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
log.info("Incoming connection from {0} accepted.", self)

View file

@ -152,9 +152,7 @@ def _attach_common_config(session, target, cwd):
@_runner
def attach_by_pid(session, target, cwd=None, wait=True):
if platform.system() != "Windows":
pytest.skip("https://github.com/microsoft/ptvsd/issues/1810")
if sys.version_info < (3,):
if sys.version_info < (3,) and platform.system() == "Windows":
pytest.skip("https://github.com/microsoft/ptvsd/issues/1811")
log.info("Attaching {0} to {1} by PID.", session, target)
@ -172,9 +170,16 @@ def attach_by_pid(session, target, cwd=None, wait=True):
if wait:
debug_me = """
import sys
while not "ptvsd" in sys.modules: pass
import threading
import time
while not "ptvsd" in sys.modules:
time.sleep(0.1)
import ptvsd
while not ptvsd.is_attached(): pass
while not ptvsd.is_attached():
time.sleep(0.1)
"""
else:
debug_me = None