mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Fix #1721 "runInTerminal" is broken on non-Windows platforms. Fix #1722: Output is not captured in "noDebug" with "runInTerminal" Groundwork for #1713: adapter: multiple concurrent sessions Move "launch" request parsing and debuggee process spawning, PID reporting and tracking, stdio "output" capture, and exit code reporting into launcher. Launcher now communicates to the adapter via a full-fledged message channel. Refactor adapter. Add an abstraction for a debug session, and treat IDE, launcher, and debug server as separate components managed by that session. Improve adapter logging to capture information about current debug session, and current message handler if any. Fix reporting exceptions from message handlers. Various test fixes.
123 lines
3.9 KiB
Python
123 lines
3.9 KiB
Python
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
# Licensed under the MIT License. See LICENSE in the project root
|
|
# for license information.
|
|
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
|
|
"""Various means of communication with the debuggee."""
|
|
|
|
import threading
|
|
import socket
|
|
|
|
from ptvsd.common import fmt, log, messaging
|
|
from tests.timeline import Request, Response
|
|
|
|
|
|
class BackChannel(object):
|
|
TIMEOUT = 20
|
|
|
|
def __init__(self, session):
|
|
self.session = session
|
|
self.port = None
|
|
self._established = threading.Event()
|
|
self._socket = None
|
|
self._server_socket = None
|
|
|
|
def __str__(self):
|
|
return fmt("backchannel-{0}", self.session.id)
|
|
|
|
def listen(self):
|
|
self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
self._server_socket.settimeout(self.TIMEOUT)
|
|
self._server_socket.bind(("127.0.0.1", 0))
|
|
_, self.port = self._server_socket.getsockname()
|
|
self._server_socket.listen(0)
|
|
|
|
def accept_worker():
|
|
log.info(
|
|
"Listening for incoming connection from {0} on port {1}...",
|
|
self,
|
|
self.port,
|
|
)
|
|
|
|
try:
|
|
self._socket, _ = self._server_socket.accept()
|
|
except socket.timeout:
|
|
raise log.exception("Timed out waiting for {0} to connect", self)
|
|
|
|
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
log.info("Incoming connection from {0} accepted.", self)
|
|
self._setup_stream()
|
|
|
|
accept_thread = threading.Thread(
|
|
target=accept_worker, name=fmt("{0} listener", self)
|
|
)
|
|
accept_thread.daemon = True
|
|
accept_thread.start()
|
|
|
|
def _setup_stream(self):
|
|
self._stream = messaging.JsonIOStream.from_socket(self._socket, name=str(self))
|
|
self._established.set()
|
|
|
|
def receive(self):
|
|
self._established.wait()
|
|
return self._stream.read_json()
|
|
|
|
def send(self, value):
|
|
self.session.timeline.unfreeze()
|
|
self._established.wait()
|
|
t = self.session.timeline.mark(("sending", value))
|
|
self._stream.write_json(value)
|
|
return t
|
|
|
|
def expect(self, expected):
|
|
actual = self.receive()
|
|
assert expected == actual, fmt(
|
|
"Test expected {0!r} on backchannel, but got {1!r} from the debuggee",
|
|
expected,
|
|
actual,
|
|
)
|
|
|
|
def close(self):
|
|
if self._socket:
|
|
log.debug("Closing {0} socket of {1}...", self, self.session)
|
|
try:
|
|
self._socket.shutdown(socket.SHUT_RDWR)
|
|
except Exception:
|
|
pass
|
|
self._socket = None
|
|
|
|
if self._server_socket:
|
|
log.debug("Closing {0} server socket of {1}...", self, self.session)
|
|
try:
|
|
self._server_socket.shutdown(socket.SHUT_RDWR)
|
|
except Exception:
|
|
pass
|
|
self._server_socket = None
|
|
|
|
|
|
class ScratchPad(object):
|
|
def __init__(self, session):
|
|
self.session = session
|
|
|
|
def __getitem__(self, key):
|
|
raise NotImplementedError
|
|
|
|
def __setitem__(self, key, value):
|
|
"""Sets debug_me.scratchpad[key] = value inside the debugged process.
|
|
"""
|
|
|
|
stackTrace_responses = self.session.all_occurrences_of(
|
|
Response(Request("stackTrace"))
|
|
)
|
|
assert (
|
|
stackTrace_responses
|
|
), 'scratchpad requires at least one "stackTrace" request in the timeline.'
|
|
stack_trace = stackTrace_responses[-1].body
|
|
frame_id = stack_trace["stackFrames"][0]["id"]
|
|
|
|
log.info("{0} debug_me.scratchpad[{1!r}] = {2!r}", self.session, key, value)
|
|
expr = fmt("__import__('debug_me').scratchpad[{0!r}] = {1!r}", key, value)
|
|
self.session.request(
|
|
"evaluate", {"frameId": frame_id, "context": "repl", "expression": expr}
|
|
)
|