mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Provide api to skip terminating child processes on terminate. Fixes #1786
This commit is contained in:
parent
3908192ce7
commit
5fcc3e4cd5
4 changed files with 71 additions and 15 deletions
|
|
@ -806,7 +806,7 @@ class PyDevdAPI(object):
|
|||
self.reapply_breakpoints(py_db)
|
||||
return ''
|
||||
|
||||
def _terminate_child_processes_windows(self):
|
||||
def _terminate_child_processes_windows(self, dont_terminate_child_pids):
|
||||
this_pid = os.getpid()
|
||||
for _ in range(50): # Try this at most 50 times before giving up.
|
||||
|
||||
|
|
@ -830,7 +830,7 @@ class PyDevdAPI(object):
|
|||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if pid != list_popen.pid:
|
||||
if pid != list_popen.pid and pid not in dont_terminate_child_pids:
|
||||
children_pids.append(pid)
|
||||
|
||||
if not children_pids:
|
||||
|
|
@ -845,7 +845,7 @@ class PyDevdAPI(object):
|
|||
|
||||
del children_pids[:]
|
||||
|
||||
def _terminate_child_processes_linux_and_mac(self):
|
||||
def _terminate_child_processes_linux_and_mac(self, dont_terminate_child_pids):
|
||||
this_pid = os.getpid()
|
||||
|
||||
def list_children_and_stop_forking(initial_pid, stop=True):
|
||||
|
|
@ -870,6 +870,8 @@ class PyDevdAPI(object):
|
|||
line = line.decode('ascii').strip()
|
||||
if line:
|
||||
pid = str(line)
|
||||
if pid in dont_terminate_child_pids:
|
||||
continue
|
||||
children_pids.append(pid)
|
||||
# Recursively get children.
|
||||
children_pids.extend(list_children_and_stop_forking(pid))
|
||||
|
|
@ -921,9 +923,9 @@ class PyDevdAPI(object):
|
|||
try:
|
||||
if py_db.terminate_child_processes:
|
||||
if IS_WINDOWS:
|
||||
self._terminate_child_processes_windows()
|
||||
self._terminate_child_processes_windows(py_db.dont_terminate_child_pids)
|
||||
else:
|
||||
self._terminate_child_processes_linux_and_mac()
|
||||
self._terminate_child_processes_linux_and_mac(py_db.dont_terminate_child_pids)
|
||||
finally:
|
||||
os._exit(0)
|
||||
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@ class PyDB(object):
|
|||
# Determines whether we should terminate child processes when asked to terminate.
|
||||
self.terminate_child_processes = True
|
||||
|
||||
# List of direct child pids which should not be terminated when terminating processes.
|
||||
self.dont_terminate_child_pids = set()
|
||||
|
||||
# These are the breakpoints received by the PyDevdAPI. They are meant to store
|
||||
# the breakpoints in the api -- its actual contents are managed by the api.
|
||||
self.api_received_breakpoints = {}
|
||||
|
|
@ -2648,14 +2651,38 @@ def settrace_forked(setup_tracing=True):
|
|||
|
||||
@contextmanager
|
||||
def skip_subprocess_arg_patch():
|
||||
'''
|
||||
May be used to skip the monkey-patching that pydevd does to
|
||||
skip changing arguments to embed the debugger into child processes.
|
||||
|
||||
i.e.:
|
||||
|
||||
with pydevd.skip_subprocess_arg_patch():
|
||||
subprocess.call(...)
|
||||
'''
|
||||
from _pydev_bundle import pydev_monkey
|
||||
with pydev_monkey.skip_subprocess_arg_patch():
|
||||
yield
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# SetupHolder
|
||||
#=======================================================================================================================
|
||||
def add_dont_terminate_child_pid(pid):
|
||||
'''
|
||||
May be used to ask pydevd to skip the termination of some process
|
||||
when it's asked to terminate (debug adapter protocol only).
|
||||
|
||||
:param int pid:
|
||||
The pid to be ignored.
|
||||
|
||||
i.e.:
|
||||
|
||||
process = subprocess.Popen(...)
|
||||
pydevd.add_dont_terminate_child_pid(process.pid)
|
||||
'''
|
||||
py_db = GetGlobalDebugger()
|
||||
if py_db is not None:
|
||||
py_db.dont_terminate_child_pids.add(pid)
|
||||
|
||||
|
||||
class SetupHolder:
|
||||
|
||||
setup = None
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ if __name__ == '__main__':
|
|||
n = int(sys.argv[-1])
|
||||
if n != 0:
|
||||
subprocess.Popen([sys.executable, __file__, 'launch-subprocesses', str(n - 1)])
|
||||
print('%screated %s (child of %s)' % ('\t' * (4 - n), os.getpid(), os.getppid()))
|
||||
if hasattr(os, 'getppid'):
|
||||
print('%screated %s (child of %s)' % ('\t' * (4 - n), os.getpid(), os.getppid()))
|
||||
else:
|
||||
print('%screated %s' % ('\t' * (4 - n), os.getpid()))
|
||||
|
||||
elif 'check-subprocesses' in sys.argv:
|
||||
elif 'check-subprocesses' in sys.argv or 'check-subprocesses-ignore-pid' in sys.argv:
|
||||
# Recursively create a process tree such as:
|
||||
# - parent (this process)
|
||||
# - p3
|
||||
|
|
@ -21,8 +24,12 @@ if __name__ == '__main__':
|
|||
# - p2
|
||||
# - p1
|
||||
# - p0
|
||||
subprocess.Popen([sys.executable, __file__, 'launch-subprocesses', '3'])
|
||||
subprocess.Popen([sys.executable, __file__, 'launch-subprocesses', '3'])
|
||||
p0 = subprocess.Popen([sys.executable, __file__, 'launch-subprocesses', '3'])
|
||||
p1 = subprocess.Popen([sys.executable, __file__, 'launch-subprocesses', '3'])
|
||||
|
||||
if 'check-subprocesses-ignore-pid' in sys.argv:
|
||||
import pydevd
|
||||
pydevd.add_dont_terminate_child_pid(p0.pid)
|
||||
|
||||
print('created', os.getpid())
|
||||
|
||||
|
|
|
|||
|
|
@ -2936,7 +2936,8 @@ def test_pydevd_systeminfo(case_setup):
|
|||
@pytest.mark.parametrize('check_subprocesses', [
|
||||
'no_subprocesses',
|
||||
'kill_subprocesses',
|
||||
'dont_kill_subprocesses'
|
||||
'kill_subprocesses_ignore_pid',
|
||||
'dont_kill_subprocesses',
|
||||
])
|
||||
def test_terminate(case_setup, scenario, check_subprocesses):
|
||||
import psutil
|
||||
|
|
@ -2948,12 +2949,15 @@ def test_terminate(case_setup, scenario, check_subprocesses):
|
|||
ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args)
|
||||
if check_subprocesses in ('kill_subprocesses', 'dont_kill_subprocesses'):
|
||||
ret.append('check-subprocesses')
|
||||
if check_subprocesses in ('kill_subprocesses_ignore_pid',):
|
||||
ret.append('check-subprocesses-ignore-pid')
|
||||
return ret
|
||||
|
||||
with case_setup.test_file(
|
||||
'_debugger_case_terminate.py',
|
||||
check_test_suceeded_msg=check_test_suceeded_msg,
|
||||
update_command_line_args=update_command_line_args,
|
||||
EXPECTED_RETURNCODE='any' if check_subprocesses == 'kill_subprocesses_ignore_pid' else 0,
|
||||
) as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
if check_subprocesses == 'dont_kill_subprocesses':
|
||||
|
|
@ -2963,7 +2967,7 @@ def test_terminate(case_setup, scenario, check_subprocesses):
|
|||
response = json_facade.write_initialize(None)
|
||||
pid = response.to_dict()['body']['pydevd']['processId']
|
||||
|
||||
if check_subprocesses in ('kill_subprocesses', 'dont_kill_subprocesses'):
|
||||
if check_subprocesses in ('kill_subprocesses', 'dont_kill_subprocesses', 'kill_subprocesses_ignore_pid'):
|
||||
process_ids_to_check = [pid]
|
||||
p = psutil.Process(pid)
|
||||
|
||||
|
|
@ -2984,7 +2988,7 @@ def test_terminate(case_setup, scenario, check_subprocesses):
|
|||
else:
|
||||
raise AssertionError('Unexpected: %s' % (scenario,))
|
||||
|
||||
if check_subprocesses in ('kill_subprocesses', 'dont_kill_subprocesses'):
|
||||
if check_subprocesses in ('kill_subprocesses', 'dont_kill_subprocesses', 'kill_subprocesses_ignore_pid'):
|
||||
|
||||
def is_pid_alive(pid):
|
||||
# Note: the process may be a zombie process in Linux
|
||||
|
|
@ -3012,6 +3016,22 @@ def test_terminate(case_setup, scenario, check_subprocesses):
|
|||
|
||||
wait_for_condition(all_pids_exited)
|
||||
|
||||
elif check_subprocesses == 'kill_subprocesses_ignore_pid':
|
||||
|
||||
def all_pids_exited():
|
||||
live_pids = get_live_pids()
|
||||
if len(live_pids) == 1:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
wait_for_condition(all_pids_exited)
|
||||
|
||||
# Now, let's kill the remaining process ourselves.
|
||||
for pid in get_live_pids():
|
||||
proc = psutil.Process(pid)
|
||||
proc.kill()
|
||||
|
||||
else: # 'dont_kill_subprocesses'
|
||||
time.sleep(1)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue