diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index 6ef173e2..35aaf1fd 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -24,8 +24,7 @@ from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint from pydevd_tracing import get_exception_traceback_str import os import subprocess -import time -from _pydev_bundle.pydev_is_thread_alive import is_thread_alive +import ctypes try: import dis @@ -808,32 +807,42 @@ class PyDevdAPI(object): self.reapply_breakpoints(py_db) return '' + def get_ppid(self): + ''' + Provides the parent pid (even for older versions of Python on Windows). + ''' + ppid = None + + try: + ppid = os.getppid() + except AttributeError: + pass + + if ppid is None and IS_WINDOWS: + ppid = self._get_windows_ppid() + + return ppid + + def _get_windows_ppid(self): + this_pid = os.getpid() + for ppid, pid in _list_ppid_and_pid(): + if pid == this_pid: + return ppid + + return None + 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. # Note: we can't kill the process itself with taskkill, so, we # list immediate children, kill that tree and then exit this process. - list_popen = self._popen( - ['wmic', 'process', 'where', '(ParentProcessId=%s)' % (this_pid,), 'get', 'ProcessId'], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - if list_popen is None: - break # We couldn't create the process. - stdout, _ = list_popen.communicate() children_pids = [] - for line in stdout.splitlines(): - line = line.strip() - if line and line != b'ProcessId': - try: - pid = int(line) - except ValueError: - pass - else: - if pid != list_popen.pid and pid not in dont_terminate_child_pids: - children_pids.append(pid) + for ppid, pid in _list_ppid_and_pid(): + if ppid == this_pid: + if pid not in dont_terminate_child_pids: + children_pids.append(pid) if not children_pids: break @@ -943,3 +952,36 @@ class PyDevdAPI(object): py_db.terminate_requested = True run_as_pydevd_daemon_thread(py_db, self._terminate_if_commands_processed, py_db) + +def _list_ppid_and_pid(): + _TH32CS_SNAPPROCESS = 0x00000002 + + class PROCESSENTRY32(ctypes.Structure): + _fields_ = [("dwSize", ctypes.c_ulong), + ("cntUsage", ctypes.c_ulong), + ("th32ProcessID", ctypes.c_ulong), + ("th32DefaultHeapID", ctypes.c_ulong), + ("th32ModuleID", ctypes.c_ulong), + ("cntThreads", ctypes.c_ulong), + ("th32ParentProcessID", ctypes.c_ulong), + ("pcPriClassBase", ctypes.c_ulong), + ("dwFlags", ctypes.c_ulong), + ("szExeFile", ctypes.c_char * 260)] + + kernel32 = ctypes.windll.kernel32 + snapshot = kernel32.CreateToolhelp32Snapshot(_TH32CS_SNAPPROCESS, 0) + ppid_and_pids = [] + try: + process_entry = PROCESSENTRY32() + process_entry.dwSize = ctypes.sizeof(PROCESSENTRY32) + if not kernel32.Process32First(snapshot, ctypes.byref(process_entry)): + pydev_log.critical('Process32First failed (getting process from CreateToolhelp32Snapshot).') + else: + while True: + ppid_and_pids.append((process_entry.th32ParentProcessID, process_entry.th32ProcessID)) + if not kernel32.Process32Next(snapshot, ctypes.byref(process_entry)): + break + finally: + kernel32.CloseHandle(snapshot) + + return ppid_and_pids diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index 9fbf0c32..124df44f 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -969,10 +969,7 @@ class PyDevJsonCommandProcessor(object): except AttributeError: pid = None - try: - ppid = os.getppid() - except AttributeError: - ppid = None + ppid = self.api.get_ppid() try: impl_desc = platform.python_implementation() diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py b/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py index 595a3dc1..50c6f55f 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py @@ -4,8 +4,9 @@ from _pydevd_bundle.pydevd_comm import pydevd_find_thread_by_id from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression from tests_python.debug_constants import IS_PY26, IS_PY3K import sys -from _pydevd_bundle.pydevd_constants import IS_CPYTHON +from _pydevd_bundle.pydevd_constants import IS_CPYTHON, IS_WINDOWS import pytest +import os def test_is_main_thread(): @@ -305,7 +306,6 @@ def test_find_main_thread_id(): _check_in_separate_process('check_fix_main_thread_id_multiple_threads', '_pydevd_test_find_main_thread_id') import subprocess - import os import pydevd cwd, environ = _build_launch_env() @@ -322,3 +322,17 @@ def test_find_main_thread_id(): env=environ, cwd=cwd ) + + +@pytest.mark.skipif(not IS_WINDOWS, reason='Windows-only test.') +def test_get_ppid(): + from _pydevd_bundle.pydevd_api import PyDevdAPI + api = PyDevdAPI() + if IS_PY3K: + # On python 3 we can check that our internal api which is used for Python 2 gives the + # same result as os.getppid. + ppid = os.getppid() + assert api._get_windows_ppid() == ppid + else: + assert api._get_windows_ppid() is not None +