Skip to content

Commit

Permalink
Use pydevd.settrace() argument instead of patch_new_process_functions…
Browse files Browse the repository at this point in the history
…() to enable multiprocessing.

Add multiprocessing= argument to attach() and enable_attach().

Refactor common code in attach() and enable_attach().

Remove broken legacy multiproc implementation.
  • Loading branch information
int19h committed Aug 24, 2019
1 parent 915515d commit 5f2cb30
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 444 deletions.
14 changes: 12 additions & 2 deletions src/ptvsd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def wait_for_attach():
return api.wait_for_attach()


def enable_attach(address, log_dir=None):
def enable_attach(address, log_dir=None, multiprocess=True):
"""Starts a DAP (Debug Adapter Protocol) server in this process,
listening for incoming socket connection from the IDE on the
specified address.
Expand All @@ -59,6 +59,11 @@ def enable_attach(address, log_dir=None):
scenarios involving multiple processes. The log file for a process
with process ID <pid> will be named "ptvsd_<pid>.log".
If multiprocess is true, ptvsd will also intercept child processes
spawned by this process, inject a debug server into them, and
configure it to attach to the same IDE before the child process
starts running any user code.
Returns the interface and the port on which the debug server is
actually listening, in the same format as address. This may be
different from address if port was 0 in the latter, in which case
Expand All @@ -73,7 +78,7 @@ def enable_attach(address, log_dir=None):
return api.enable_attach(address, log_dir)


def attach(address, log_dir=None):
def attach(address, log_dir=None, multiprocess=True):
"""Starts a DAP (Debug Adapter Protocol) server in this process,
and connects it to the IDE that is listening for an incoming
connection on a socket with the specified address.
Expand All @@ -87,6 +92,11 @@ def attach(address, log_dir=None):
scenarios involving multiple processes. The log file for a process
with process ID <pid> will be named "ptvsd_<pid>.log".
If multiprocess is true, ptvsd will also intercept child processes
spawned by this process, inject a debug server into them, and
configure it to attach to the same IDE before the child process
starts running any user code.
This function doesn't return until connection to the IDE has been
established.
"""
Expand Down
7 changes: 0 additions & 7 deletions src/ptvsd/_vendored/force_pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,3 @@ def ptvsd_breakpointhook():
from _pydevd_bundle import pydevd_constants
from _pydevd_bundle import pydevd_defaults
pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = pydevd_constants.HTTP_JSON_PROTOCOL

# Ensure our patch args is used. This is invoked when a child process is spawned
# with multiproc debugging enabled.
from _pydev_bundle import pydev_monkey
from ptvsd.server import multiproc
pydev_monkey.patch_args = multiproc.patch_and_quote_args
pydev_monkey.patch_new_process_functions()
2 changes: 2 additions & 0 deletions src/ptvsd/_vendored/pydevd/pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,7 @@ def _enable_attach(
address,
dont_trace_start_patterns=(),
dont_trace_end_paterns=(),
patch_multiprocessing=False,
):
'''
Starts accepting connections at the given host/port. The debugger will not be initialized nor
Expand All @@ -2138,6 +2139,7 @@ def _enable_attach(
block_until_connected=False,
dont_trace_start_patterns=dont_trace_start_patterns,
dont_trace_end_paterns=dont_trace_end_paterns,
patch_multiprocessing=patch_multiprocessing,
)
py_db = get_global_debugger()
py_db.wait_for_server_socket_ready()
Expand Down
98 changes: 43 additions & 55 deletions src/ptvsd/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,6 @@
)


def _get_dont_trace_patterns():
ptvsd_path, _, _ = get_abs_path_real_path_and_base_from_file(ptvsd.__file__)
ptvsd_path = os.path.dirname(ptvsd_path)
start_patterns = (ptvsd_path,)
end_patterns = ("ptvsd_launcher.py",)
log.info(
"Won't trace filenames starting with: {0!j}\n"
"Won't trace filenames ending with: {1!j}",
start_patterns,
end_patterns,
)
return start_patterns, end_patterns


def wait_for_attach():
log.info("wait_for_attach()")
dbg = get_global_debugger()
Expand All @@ -46,59 +32,62 @@ def wait_for_attach():
pydevd._wait_for_attach(cancel=cancel_event)


def enable_attach(address, log_dir=None):
if log_dir:
common_opts.log_dir = log_dir
log.to_file()
log.info("enable_attach{0!r}", (address,))
def _starts_debugging(func):
def debug(address, log_dir=None, multiprocess=True):
if log_dir:
common_opts.log_dir = log_dir

if is_attached():
log.info("enable_attach() ignored - already attached.")
return None, None
log.to_file()
log.info("{0}{1!r}", func.__name__, (address, log_dir, multiprocess))

# Ensure port is int
host, port = address
address = (host, int(port))
if is_attached():
log.info("{0}() ignored - already attached.", func.__name__)
return server_opts.host, server_opts.port

start_patterns, end_patterns = _get_dont_trace_patterns()
server_opts.host, server_opts.port = pydevd._enable_attach(
address,
dont_trace_start_patterns=start_patterns,
dont_trace_end_paterns=end_patterns,
)
# Ensure port is int
if address is not server_opts:
host, port = address
server_opts.host, server_opts.port = (host, int(port))

if server_opts.subprocess_notify:
from ptvsd.server import multiproc
multiproc.notify_root(server_opts.port)
if multiprocess is not server_opts:
server_opts.multiprocess = multiprocess

return (server_opts.host, server_opts.port)
ptvsd_path, _, _ = get_abs_path_real_path_and_base_from_file(ptvsd.__file__)
ptvsd_path = os.path.dirname(ptvsd_path)
start_patterns = (ptvsd_path,)
end_patterns = ("ptvsd_launcher.py",)
log.info(
"Won't trace filenames starting with: {0!j}\n"
"Won't trace filenames ending with: {1!j}",
start_patterns,
end_patterns,
)

return func(start_patterns, end_patterns)

def attach(address, log_dir=None):
if log_dir:
common_opts.log_dir = log_dir
log.to_file()
log.info("attach{0!r}", (address,))
return debug

if is_attached():
log.info("attach() ignored - already attached.")
return

# Ensure port is int
host, port = address
address = (host, int(port))
server_opts.host, server_opts.port = address
@_starts_debugging
def enable_attach(dont_trace_start_patterns, dont_trace_end_patterns):
server_opts.host, server_opts.port = pydevd._enable_attach(
(server_opts.host, server_opts.port),
dont_trace_start_patterns=dont_trace_start_patterns,
dont_trace_end_paterns=dont_trace_end_patterns,
patch_multiprocessing=server_opts.multiprocess,
)
return server_opts.host, server_opts.port

start_patterns, end_patterns = _get_dont_trace_patterns()

log.debug("pydevd.settrace()")
@_starts_debugging
def attach(dont_trace_start_patterns, dont_trace_end_patterns):
pydevd.settrace(
host=host,
port=port,
host=server_opts.host,
port=server_opts.port,
suspend=False,
patch_multiprocessing=server_opts.multiprocess,
dont_trace_start_patterns=start_patterns,
dont_trace_end_paterns=end_patterns,
dont_trace_start_patterns=dont_trace_start_patterns,
dont_trace_end_paterns=dont_trace_end_patterns,
)


Expand All @@ -119,8 +108,7 @@ def break_into_debugger():
while (
stop_at_frame is not None
and global_debugger.get_file_type(
stop_at_frame,
get_abs_path_real_path_and_base_from_frame(stop_at_frame)
stop_at_frame, get_abs_path_real_path_and_base_from_frame(stop_at_frame)
)
== global_debugger.PYDEV_FILE
):
Expand Down
85 changes: 32 additions & 53 deletions src/ptvsd/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import ptvsd
from ptvsd.common import compat, fmt, log, options as common_opts
from ptvsd.server import multiproc, options
from ptvsd.server import options


TARGET = "<filename> | -m <module> | -c <code> | --pid <pid>"
Expand Down Expand Up @@ -172,63 +172,22 @@ def parse(args):
return it


def setup_connection():
pydevd.apply_debugger_options(
{
"server": not options.client,
"client": options.host,
"port": options.port,
"multiprocess": options.multiprocess,
}
)

if options.multiprocess:
multiproc.listen_for_subprocesses()

def setup_debug_server(argv_0):
# We need to set up sys.argv[0] before invoking attach() or enable_attach(),
# because they use it to report the 'process' event. Thus, we can't rely on
# run_path() and run_module() doing that, even though they will eventually.

if options.target_kind == "code":
sys.argv[0] = "-c"
elif options.target_kind == "file":
sys.argv[0] = options.target
elif options.target_kind == "module":
# Add current directory to path, like Python itself does for -m. This must
# be in place before trying to use find_spec below to resolve submodules.
sys.path.insert(0, "")

# We want to do the same thing that run_module() would do here, without
# actually invoking it. On Python 3, it's exposed as a public API, but
# on Python 2, we have to invoke a private function in runpy for this.
# Either way, if it fails to resolve for any reason, just leave argv as is.
try:
if sys.version_info >= (3,):
from importlib.util import find_spec

spec = find_spec(options.target)
if spec is not None:
sys.argv[0] = spec.origin
else:
_, _, _, sys.argv[0] = runpy._get_module_details(options.target)
except Exception:
log.exception("Error determining module path for sys.argv")
else:
assert False
sys.argv[0] = compat.filename(argv_0)
log.debug("sys.argv after patching: {0!r}", sys.argv)

addr = (options.host, options.port)
if options.client:
ptvsd.attach(addr)
else:
ptvsd.enable_attach(addr)
debug = ptvsd.attach if options.client else ptvsd.enable_attach
debug(address=options, multiprocess=options)

if options.wait:
ptvsd.wait_for_attach()


def run_file():
setup_connection()
setup_debug_server(options.target)

# run_path has one difference with invoking Python from command-line:
# if the target is a file (rather than a directory), it does not add its
Expand All @@ -246,7 +205,27 @@ def run_file():


def run_module():
setup_connection()
# Add current directory to path, like Python itself does for -m. This must
# be in place before trying to use find_spec below to resolve submodules.
sys.path.insert(0, "")

# We want to do the same thing that run_module() would do here, without
# actually invoking it. On Python 3, it's exposed as a public API, but
# on Python 2, we have to invoke a private function in runpy for this.
# Either way, if it fails to resolve for any reason, just leave argv as is.
try:
if sys.version_info >= (3,):
from importlib.util import find_spec

spec = find_spec(options.target)
if spec is not None:
argv_0 = spec.origin
else:
_, _, _, argv_0 = runpy._get_module_details(options.target)
except Exception:
log.exception("Error determining module path for sys.argv")

setup_debug_server(argv_0)

# On Python 2, module name must be a non-Unicode string, because it ends up
# a part of module's __package__, and Python will refuse to run the module
Expand Down Expand Up @@ -281,7 +260,8 @@ def run_code():
# Add current directory to path, like Python itself does for -c.
sys.path.insert(0, "")
code = compile(options.target, "<string>", "exec")
setup_connection()

setup_debug_server("-c")
eval(code, {})


Expand All @@ -303,6 +283,7 @@ def escape(ch):
host = quoted_str(options.host)
port = options.port
client = options.client
multiprocess = options.multiprocess
log_dir = quoted_str(ptvsd.common.options.log_dir)

ptvsd_path = os.path.abspath(os.path.join(ptvsd.server.__file__, "../.."))
Expand All @@ -325,10 +306,8 @@ def escape(ch):
log.to_file()
log.info("Bootstrapping injected debugger.")
if {client}:
ptvsd.attach(({host}, {port}))
else:
ptvsd.enable_attach(({host}, {port}))
debug = ptvsd.attach if {client} else ptvsd.enable_attach
debug(({host}, {port}), multiprocess={multiprocess})
""".format(
**locals()
)
Expand Down
Loading

0 comments on commit 5f2cb30

Please sign in to comment.