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

Reverse the connection direction used by enable attach internals. #1833

Merged
merged 3 commits into from
Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sonar.sources=src/ptvsd
sonar.exclusions=src/ptvsd/_vendored/**/*,pytests/**/*,src/ptvsd/_version.py
sonar.exclusions=src/ptvsd/_vendored/**/*,src/ptvsd/_version.py
sonar.cfamily.build-wrapper-output.bypass=true
sonar.c.file.suffixes=-
sonar.cpp.file.suffixes=-
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sonar.sources=src/ptvsd
sonar.exclusions=src/ptvsd/_vendored/**/*,pytests/**/*,src/ptvsd/_version.py
sonar.exclusions=src/ptvsd/_vendored/**/*,src/ptvsd/_version.py
sonar.cfamily.build-wrapper-output.bypass=true
sonar.c.file.suffixes=-
sonar.cpp.file.suffixes=-
Expand Down
1 change: 1 addition & 0 deletions src/ptvsd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"enable_attach",
"is_attached",
"wait_for_attach",
"tracing",
]

import codecs
Expand Down
12 changes: 8 additions & 4 deletions src/ptvsd/_vendored/pydevd/pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2253,6 +2253,7 @@ def _enable_attach(
patch_multiprocessing=False,
access_token=None,
ide_access_token=None,
as_client=False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is already merged, but I don't think we should do this change on pydevd... the API to connect as a client is calling settrace directly with the proper host/port, whereas _enable_attach() was really only meant to be called to act as a server and wait until a connection was done, so, I think the changes on pydevd should be reverted and ptvsd should just use settrace directly here (the code related to the checking the port is also not interesting in this case... if it's starting a server it should be checked, but if it'll connect to a port in the client it could connect one time to one port and later on connect to another port).

):
'''
Starts accepting connections at the given host/port. The debugger will not be initialized nor
Expand All @@ -2275,16 +2276,19 @@ def _enable_attach(
port=port,
suspend=False,
wait_for_ready_to_run=False,
block_until_connected=False,
block_until_connected=as_client,
dont_trace_start_patterns=dont_trace_start_patterns,
dont_trace_end_paterns=dont_trace_end_paterns,
patch_multiprocessing=patch_multiprocessing,
access_token=access_token,
ide_access_token=ide_access_token,
)
py_db = get_global_debugger()
py_db.wait_for_server_socket_ready()
return py_db._server_socket_name

if not as_client:
py_db = get_global_debugger()
py_db.wait_for_server_socket_ready()
return py_db._server_socket_name
return (host, port)


def _wait_for_attach(cancel=None):
Expand Down
31 changes: 22 additions & 9 deletions src/ptvsd/adapter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import absolute_import, print_function, unicode_literals

import argparse
import json
import locale
import os
import sys
Expand Down Expand Up @@ -33,11 +34,25 @@ def main(args):
if args.port is None:
session.connect_to_ide()
else:
if args.for_server_on_port is not None:
session.connect_to_server(("127.0.0.1", args.for_server_on_port))
with session.accept_connection_from_ide((args.host, args.port)) as (_, port):
if session.server:
session.server.set_debugger_property({"adapterPort": port})
if args.for_enable_attach:
# Users may want the adapter to choose the port for them, by setting port==0.
# For example, the Python Data Science extension uses this mode in enable_attach.
# Let enable_attach know the port that users should use to connect to the adapter.
with session.accept_connection_from_ide((args.host, args.port)) as (adapter_host, adapter_port):
# This mode is used only for enable_attach. Here, we always connect to
# adapter from the debug server as client. Adapter needs to start a listener
# and provide that port to debug server.
with session.accept_connection_from_server() as (server_host, server_port):
connection_details = {
"adapter": {"host": adapter_host, "port": adapter_port},
"server": {"host": server_host, "port": server_port}
}
log.info("Writing to stdout for enable_attach: {0!r}", connection_details)
print(json.dumps(connection_details))
sys.stdout.flush()
else:
with session.accept_connection_from_ide((args.host, args.port)) as (_, adapter_port):
pass
session.wait_for_completion()


Expand All @@ -61,10 +76,8 @@ def _parse_argv(argv):
)

parser.add_argument(
"--for-server-on-port",
type=int,
default=None,
metavar="PORT",
"--for-enable-attach",
action="store_true",
help=argparse.SUPPRESS,
)

Expand Down
89 changes: 43 additions & 46 deletions src/ptvsd/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
import ptvsd
from ptvsd.common import log, options as common_opts
from ptvsd.server import options as server_opts
from ptvsd.common.compat import queue
from _pydevd_bundle.pydevd_constants import get_global_debugger
from pydevd_file_utils import get_abs_path_real_path_and_base_from_file


_QUEUE_TIMEOUT = 10
_ADAPTER_PATH = os.path.join(os.path.dirname(ptvsd.__file__), "adapter")


def wait_for_attach():
Expand Down Expand Up @@ -80,56 +80,53 @@ def enable_attach(dont_trace_start_patterns, dont_trace_end_patterns):
if hasattr(enable_attach, "called"):
raise RuntimeError("enable_attach() can only be called once per process.")

host, port = pydevd._enable_attach(
("127.0.0.1", 0),
import subprocess
adapter_args = [
sys.executable,
_ADAPTER_PATH,
"--host",
server_opts.host,
"--port",
str(server_opts.port),
"--for-enable-attach",
]

if common_opts.log_dir is not None:
adapter_args += ["--log-dir", common_opts.log_dir]

log.info("enable_attach() spawning adapter: {0!r}", adapter_args)

# Adapter life time is expected to be longer than this process,
# so never wait on the adapter process
process = subprocess.Popen(
adapter_args,
bufsize=0,
stdout=subprocess.PIPE,
)

line = process.stdout.readline()
if isinstance(line, bytes):
line = line.decode("utf-8")
connection_details = json.JSONDecoder().decode(line)
log.info("Connection details received from adapter: {0!r}", connection_details)

host = "127.0.0.1" # This should always be loopback address.
port = connection_details["server"]["port"]
address = ("127.0.0.1", port)

pydevd._enable_attach(
address,
dont_trace_start_patterns=dont_trace_start_patterns,
dont_trace_end_paterns=dont_trace_end_patterns,
patch_multiprocessing=server_opts.multiprocess,
as_client=True,
)

log.info("pydevd debug server running at: {0}:{1}", host, port)

class _DAPMessagesListener(pydevd.IDAPMessagesListener):
def before_send(self, msg):
pass

def after_receive(self, msg):
try:
if msg["command"] == "setDebuggerProperty":
port_queue.put(msg["arguments"]["adapterPort"])
except KeyError:
pass

port_queue = queue.Queue()
pydevd.add_dap_messages_listener(_DAPMessagesListener())

with pydevd.skip_subprocess_arg_patch():
import subprocess

adapter_args = [
sys.executable,
os.path.join(os.path.dirname(ptvsd.__file__), "adapter"),
"--host",
server_opts.host,
"--port",
str(server_opts.port),
"--for-server-on-port",
str(port),
]

if common_opts.log_dir is not None:
adapter_args += ["--log-dir", common_opts.log_dir]

log.info("enable_attach() spawning adapter: {0!r}", adapter_args)

# Adapter life time is expected to be longer than this process,
# so never wait on the adapter process
process = subprocess.Popen(adapter_args, bufsize=0)
# Ensure that we ignore the adapter process when terminating the
# debugger.
pydevd.add_dont_terminate_child_pid(process.pid)

server_opts.port = port_queue.get(True, _QUEUE_TIMEOUT)
log.info("pydevd debug client connected to: {0}:{1}", host, port)

# Ensure that we ignore the adapter process when terminating the debugger.
pydevd.add_dont_terminate_child_pid(process.pid)
server_opts.port = connection_details["adapter"]["port"]

listener_file = os.getenv("PTVSD_LISTENER_FILE")
if listener_file is not None:
Expand Down
6 changes: 2 additions & 4 deletions tests/debug/comms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import threading
import socket

from ptvsd.common import fmt, log, messaging
from ptvsd.common import fmt, log, messaging, sockets
from tests.timeline import Request, Response


Expand All @@ -27,9 +27,7 @@ def __str__(self):
return fmt("{0}.backchannel", self.session.debuggee_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._server_socket = sockets.create_server("127.0.0.1", 0, self.TIMEOUT)
_, self.port = self._server_socket.getsockname()
self._server_socket.listen(0)

Expand Down