Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
tdesveaux committed Aug 23, 2024
1 parent a5d551d commit 7753f10
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 24 deletions.
88 changes: 64 additions & 24 deletions nimp/base_commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@
import re
import shutil
import time
from typing import Sequence

import psutil

import nimp.command
import nimp.sys.platform
import nimp.sys.process
from nimp.environment import Environment as NimpEnvironment

class Check(nimp.command.CommandGroup):
''' Check related commands '''
Expand Down Expand Up @@ -124,8 +128,11 @@ def _show_nimp_environment(env):


class _Processes(CheckCommand):
def __init__(self):
super(_Processes, self).__init__()

PROCESS_IGNORE_PATTERNS: Sequence[re.Pattern] = (
# re.compile(r'^CrashReportClient\.exe$', re.IGNORECASE),
re.compile(r'^dotnet\.exe$', re.IGNORECASE),
)

def configure_arguments(self, env, parser):
parser.add_argument('-k', '--kill',
Expand All @@ -137,55 +144,88 @@ def configure_arguments(self, env, parser):
default=[os.path.normpath(f'{os.path.abspath(env.root_dir)}/*')])
return True

def _run_check(self, env):
def _run_check(self, env: NimpEnvironment):
logging.info('Checking running processes…')

# Irrelevant on sane Unix platforms
if not nimp.sys.platform.is_windows():
logging.warning("Command only available on Windows platform")
return True

# Irrelevant if we’re not an Unreal project
if not hasattr(env, 'is_unreal') or not env.is_unreal:
if not getattr(env, 'is_unreal', False):
logging.warning("Command only available in an Unreal project context")
return True

print(f"[DEBUG] ROOTDIR: {os.path.abspath(env.root_dir)}")

# Find all running binaries launched from the project directory
# and optionally kill them, unless they’re in the exception list.
# We get to try 5 times just in case
for _ in range(5):
found_problem = False

# TODO(tdesveaux): remove sanity check psutil parity
processes = _Processes._list_windows_processes()

for pid, info in processes.items():
if not any(fnmatch.fnmatch(info[0], filter) for filter in env.filters):
checked_processes_count = 0
killed_processes: list[psutil.Process] = []

psutil.process_iter.cache_clear()
current_process = psutil.Process()
logging.debug('Current process %s (%s)', current_process.pid, current_process.exe())
found_problem = True
if (parent_process := current_process.parent()) is not None:
# TODO(tdesveaux): remove sanity check psutil parity
assert current_process.pid not in processes or processes[current_process.pid][1] == parent_process.pid
logging.debug('\tParent is %s (%s)', parent_process.pid, parent_process.exe())

ignore_process_ids = set((
# ignore special process 0
0,
current_process.pid,
))
ignore_process_ids.update((p.pid for p in current_process.children(recursive=True)))
logging.debug("Ignore processes: %s", ignore_process_ids)
for process in psutil.process_iter():
if process.pid in ignore_process_ids:
continue
process_basename = os.path.basename(info[0])
processes_ignore_patterns = _Processes.get_processes_ignore_patterns()
if any([re.match(p, process_basename, re.IGNORECASE) for p in processes_ignore_patterns]):
logging.info(f'process {pid} {info[0]} will be kept alive')

checked_processes_count += 1
process_executable_path = process.exe()
# TODO(tdesveaux): remove sanity check psutil parity
assert process.pid not in processes or process_executable_path == processes[process.pid][0]
if not any(fnmatch.fnmatch(process_executable_path, filter) for filter in env.filters):
continue
logging.warning('Found problematic process %s (%s)', pid, info[0])

process_basename = os.path.basename(process_executable_path)
if any(p.match(process_basename) for p in _Processes.PROCESS_IGNORE_PATTERNS):
logging.info('process %s (%s) will be kept alive', process.pid, process_executable_path)
continue
logging.warning('Found problematic process %s (%s)', process.pid, process_executable_path)
found_problem = True
if info[1] in processes:
logging.warning('Parent is %s (%s)', info[1], processes[info[1]][0])
if (parent_process := process.parent()) is not None:
# TODO(tdesveaux): remove sanity check psutil parity
assert process.pid not in processes or processes[process.pid][1] == parent_process.pid
logging.warning('\tParent is %s (%s)', parent_process.pid, parent_process.exe())

if env.kill:
logging.info('Killing process…')
nimp.sys.process.call(['wmic', 'process', 'where', 'processid=' + pid, 'delete'])
logging.info('%s processes checked.', len(processes))
logging.info('Killing process %s...', process.pid)
process.kill()
killed_processes.append(process)

logging.info('%d processes checked.', checked_processes_count)
if not env.kill:
return not found_problem
if not found_problem:
return True
time.sleep(5)
if killed_processes:
killed_processes[0].wait(timeout=5)
else:
time.sleep(5)

return False

@staticmethod
def get_processes_ignore_patterns():
return [
# r'^CrashReportClient\.exe$',
r'^dotnet\.exe$',
]

@staticmethod
def _list_windows_processes():
processes = {}
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def _try_get_revision():
'giteapy',
# FIXME: sort out what is required by nimp-cli and what could be in nimp-dne
'jira',
'psutil==6.0.0',
],

entry_points = {
Expand Down

0 comments on commit 7753f10

Please sign in to comment.