mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
* Fix #810: Sub-Process: monkey patch pydevd to start process using ptvsd * Add --multiprocess to ParseArgsTests tests. * Pass port range down to child processes, and let them pick a port themselves. * Remove ptvsd.port and replace with debugging output. Pick ports from the range randomly rather than always scanning start to end, to reduce collisions.
This commit is contained in:
parent
fc921c0aef
commit
55059ca379
4 changed files with 103 additions and 16 deletions
|
|
@ -6,6 +6,7 @@ import argparse
|
|||
import os.path
|
||||
import sys
|
||||
|
||||
from ptvsd import pydevd_hooks
|
||||
from ptvsd._attach import attach_main
|
||||
from ptvsd._local import debug_main, run_main
|
||||
from ptvsd.socket import Address
|
||||
|
|
@ -34,7 +35,6 @@ PYDEVD_FLAGS = {
|
|||
'--DEBUG_RECORD_SOCKET_READS',
|
||||
'--cmd-line',
|
||||
'--module',
|
||||
'--multiproc',
|
||||
'--multiprocess',
|
||||
'--print-in-debugger-startup',
|
||||
'--save-signatures',
|
||||
|
|
@ -44,12 +44,6 @@ PYDEVD_FLAGS = {
|
|||
'--qt-support=auto',
|
||||
}
|
||||
|
||||
USAGE = """
|
||||
{0} [-h] [-V] [--nodebug] [--host HOST | --server-host HOST] --port PORT -m MODULE [arg ...]
|
||||
{0} [-h] [-V] [--nodebug] [--host HOST | --server-host HOST] --port PORT FILENAME [arg ...]
|
||||
{0} [-h] [-V] --host HOST --port PORT --pid PROCESS_ID
|
||||
""" # noqa
|
||||
|
||||
|
||||
def parse_args(argv=None):
|
||||
"""Return the parsed args to use in main()."""
|
||||
|
|
@ -131,7 +125,7 @@ def _group_args(argv):
|
|||
supported.append(arg)
|
||||
|
||||
# ptvsd support
|
||||
elif arg in ('--host', '--server-host', '--port', '--pid', '-m'):
|
||||
elif arg in ('--host', '--server-host', '--port', '--pid', '-m', '--multiprocess-port-range'):
|
||||
if arg == '-m' or arg == '--pid':
|
||||
gottarget = True
|
||||
supported.append(arg)
|
||||
|
|
@ -153,10 +147,7 @@ def _group_args(argv):
|
|||
|
||||
|
||||
def _parse_args(prog, argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=prog,
|
||||
usage=USAGE.format(prog),
|
||||
)
|
||||
parser = argparse.ArgumentParser(prog=prog)
|
||||
|
||||
parser.add_argument('--nodebug', action='store_true')
|
||||
|
||||
|
|
@ -165,6 +156,15 @@ def _parse_args(prog, argv):
|
|||
host.add_argument('--server-host')
|
||||
parser.add_argument('--port', type=int, required=True)
|
||||
|
||||
def port_range(arg):
|
||||
arg = tuple(int(s) for s in arg.split('-'))
|
||||
if len(arg) != 2:
|
||||
raise ValueError
|
||||
return arg
|
||||
|
||||
parser.add_argument('--multiprocess', action='store_true')
|
||||
parser.add_argument('--multiprocess-port-range', type=port_range)
|
||||
|
||||
target = parser.add_mutually_exclusive_group(required=True)
|
||||
target.add_argument('-m', dest='module')
|
||||
target.add_argument('--pid', type=int)
|
||||
|
|
@ -191,6 +191,10 @@ def _parse_args(prog, argv):
|
|||
else:
|
||||
args.address = Address.as_client(clienthost, ns.pop('port'))
|
||||
|
||||
multiprocess_port_range = ns.pop('multiprocess_port_range')
|
||||
if multiprocess_port_range is not None:
|
||||
pydevd_hooks.multiprocess_port_range = multiprocess_port_range
|
||||
|
||||
pid = ns.pop('pid')
|
||||
module = ns.pop('module')
|
||||
filename = ns.pop('filename')
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def _pydevd_settrace(redirect_output=None, _pydevd=pydevd, **kwargs):
|
|||
# Then move at least some parts to the appropriate modules. This module
|
||||
# is focused on running the debugger.
|
||||
|
||||
global_next_session = None
|
||||
global_next_session = lambda: None
|
||||
|
||||
|
||||
def enable_attach(address, redirect_output=True,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,23 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pydevd
|
||||
from _pydev_bundle import pydev_monkey
|
||||
from _pydevd_bundle import pydevd_comm
|
||||
|
||||
import ptvsd
|
||||
from ptvsd.socket import Address
|
||||
from ptvsd.daemon import Daemon, DaemonStoppedError, DaemonClosedError
|
||||
from ptvsd._util import debug, new_hidden_thread
|
||||
|
||||
|
||||
# The intersection of default ephemeral port ranges for various common systems.
|
||||
multiprocess_port_range = (49152, 61000)
|
||||
|
||||
|
||||
def start_server(daemon, host, port, **kwargs):
|
||||
"""Return a socket to a (new) local pydevd-handling daemon.
|
||||
|
||||
|
|
@ -67,7 +75,55 @@ def start_client(daemon, host, port, **kwargs):
|
|||
return sock
|
||||
|
||||
|
||||
def install(pydevd, address,
|
||||
# See pydevd/_vendored/pydevd/_pydev_bundle/pydev_monkey.py
|
||||
def get_python_c_args(host, port, indC, args, setup):
|
||||
runner = '''
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
sys.path.append(r'{ptvsd_syspath}')
|
||||
|
||||
import ptvsd
|
||||
from ptvsd._util import DEBUG
|
||||
from ptvsd import pydevd_hooks
|
||||
|
||||
pydevd_hooks.multiprocess_port_range = ({first_port}, {last_port})
|
||||
|
||||
from _pydev_bundle import pydev_monkey
|
||||
pydev_monkey.patch_new_process_functions()
|
||||
|
||||
ports = list(range({first_port}, {last_port}))
|
||||
random.shuffle(ports)
|
||||
for port in ports:
|
||||
try:
|
||||
ptvsd.enable_attach(('localhost', port))
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
if DEBUG:
|
||||
print('Child process %d listening on port %d' % (os.getpid(), port))
|
||||
break
|
||||
else:
|
||||
raise Exception('Could not find a free port in range {first_port}-{last_port}')
|
||||
|
||||
ptvsd.wait_for_attach()
|
||||
{rest}
|
||||
'''
|
||||
|
||||
first_port, last_port = multiprocess_port_range
|
||||
|
||||
# __file__ will be .../ptvsd/__init__.py, and we want the ...
|
||||
ptvsd_syspath = os.path.join(ptvsd.__file__, '../..')
|
||||
|
||||
return runner.format(
|
||||
first_port=first_port,
|
||||
last_port=last_port,
|
||||
ptvsd_syspath=ptvsd_syspath,
|
||||
rest=args[indC + 1])
|
||||
|
||||
|
||||
def install(pydevd_module, address,
|
||||
start_server=start_server, start_client=start_client,
|
||||
**kwargs):
|
||||
"""Configure pydevd to use our wrapper.
|
||||
|
|
@ -89,12 +145,16 @@ def install(pydevd, address,
|
|||
pydevd_comm.start_server = _start_server
|
||||
pydevd_comm.start_client = _start_client
|
||||
|
||||
# This is invoked when a child process is spawned with multiproc debugging enabled.
|
||||
pydev_monkey._get_python_c_args = get_python_c_args
|
||||
|
||||
# Ensure that pydevd is using our functions.
|
||||
pydevd.start_server = _start_server
|
||||
pydevd.start_client = _start_client
|
||||
pydevd_module.start_server = _start_server
|
||||
pydevd_module.start_client = _start_client
|
||||
__main__ = sys.modules['__main__']
|
||||
if __main__ is not pydevd:
|
||||
if getattr(__main__, '__file__', None) == pydevd.__file__:
|
||||
__main__.start_server = _start_server
|
||||
__main__.start_client = _start_client
|
||||
|
||||
return daemon
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -76,6 +79,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -94,6 +98,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -112,6 +117,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -130,6 +136,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -148,6 +155,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -167,6 +175,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -185,6 +194,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': True,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -204,6 +214,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': True,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -223,6 +234,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': True,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -249,6 +261,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, [
|
||||
'--DEBUG',
|
||||
|
|
@ -286,6 +299,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, [
|
||||
'--DEBUG',
|
||||
|
|
@ -314,6 +328,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -342,6 +357,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -361,6 +377,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -379,6 +396,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -398,6 +416,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -415,6 +434,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -433,6 +453,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -451,6 +472,7 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': False,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, ['--module'] + self.EXPECTED_EXTRA)
|
||||
|
||||
|
|
@ -470,5 +492,6 @@ class ParseArgsTests(unittest.TestCase):
|
|||
'nodebug': True,
|
||||
'single_session': False,
|
||||
'wait': False,
|
||||
'multiprocess': False,
|
||||
})
|
||||
self.assertEqual(extra, ['--module'] + self.EXPECTED_EXTRA)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue