Report ppid on pydevdSystemInfo on Python 2.7 on Windows. Fixes #1877

This commit is contained in:
Fabio Zadrozny 2019-10-29 16:31:53 -03:00
parent 88db1db271
commit fe988d8149
3 changed files with 79 additions and 26 deletions

View file

@ -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

View file

@ -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()

View file

@ -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