From 9cebcb3c62e0f4144458b99e4a972fe3c9c73971 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Tue, 30 May 2023 12:09:09 -0500 Subject: [PATCH] Add timeouts to engine processes --- .gitignore | 1 + lib/render_engines/aerender_engine.py | 2 +- lib/render_engines/base_engine.py | 6 ++++-- lib/render_engines/blender_engine.py | 19 ++++++++++--------- lib/render_engines/ffmpeg_engine.py | 9 ++++++--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 99e2a8a..f3206bb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.pyc /server_state.json /.scheduler_prefs +/database.db diff --git a/lib/render_engines/aerender_engine.py b/lib/render_engines/aerender_engine.py index 6c95da5..e602038 100644 --- a/lib/render_engines/aerender_engine.py +++ b/lib/render_engines/aerender_engine.py @@ -14,7 +14,7 @@ class AERender(BaseRenderEngine): try: render_path = cls.renderer_path() if render_path: - ver_out = subprocess.check_output([render_path, '-version']) + ver_out = subprocess.check_output([render_path, '-version'], timeout=SUBPROCESS_TIMEOUT) version = ver_out.decode('utf-8').split(" ")[-1].strip() except Exception as e: logger.error(f'Failed to get {cls.name()} version: {e}') diff --git a/lib/render_engines/base_engine.py b/lib/render_engines/base_engine.py index 1d36600..1d4d8c7 100644 --- a/lib/render_engines/base_engine.py +++ b/lib/render_engines/base_engine.py @@ -3,6 +3,7 @@ import os import subprocess logger = logging.getLogger() +SUBPROCESS_TIMEOUT = 5 class BaseRenderEngine(object): @@ -18,7 +19,7 @@ class BaseRenderEngine(object): def renderer_path(cls): path = None try: - path = subprocess.check_output(['which', cls.name()]).decode('utf-8').strip() + path = subprocess.check_output(['which', cls.name()], timeout=SUBPROCESS_TIMEOUT).decode('utf-8').strip() except subprocess.CalledProcessError: for p in cls.install_paths: if os.path.exists(p): @@ -36,7 +37,8 @@ class BaseRenderEngine(object): path = cls.renderer_path() if not path: raise FileNotFoundError("renderer path not found") - help_doc = subprocess.check_output([path, '-h'], stderr=subprocess.STDOUT).decode('utf-8') + help_doc = subprocess.check_output([path, '-h'], stderr=subprocess.STDOUT, + timeout=SUBPROCESS_TIMEOUT).decode('utf-8') return help_doc @classmethod diff --git a/lib/render_engines/blender_engine.py b/lib/render_engines/blender_engine.py index 198ac02..c217168 100644 --- a/lib/render_engines/blender_engine.py +++ b/lib/render_engines/blender_engine.py @@ -17,7 +17,7 @@ class Blender(BaseRenderEngine): try: render_path = cls.renderer_path() if render_path: - ver_out = subprocess.check_output([render_path, '-v']) + ver_out = subprocess.check_output([render_path, '-v'], timeout=SUBPROCESS_TIMEOUT) version = ver_out.decode('utf-8').splitlines()[0].replace('Blender', '').strip() except Exception as e: logger.error(f'Failed to get Blender version: {e}') @@ -30,11 +30,11 @@ class Blender(BaseRenderEngine): return formats @classmethod - def run_python_expression(cls, project_path, python_expression): + def run_python_expression(cls, project_path, python_expression, timeout=None): if os.path.exists(project_path): try: return subprocess.run([cls.renderer_path(), '-b', project_path, '--python-expr', python_expression], - capture_output=True) + capture_output=True, timeout=timeout) except Exception as e: logger.warning(f"Error running python expression in blender: {e}") pass @@ -42,11 +42,11 @@ class Blender(BaseRenderEngine): raise FileNotFoundError(f'Project file not found: {project_path}') @classmethod - def run_python_script(cls, project_path, script_path): + def run_python_script(cls, project_path, script_path, timeout=None): if os.path.exists(project_path) and os.path.exists(script_path): try: return subprocess.run([cls.renderer_path(), '-b', project_path, '--python', script_path], - capture_output=True) + capture_output=True, timeout=timeout) except Exception as e: logger.warning(f"Error running python expression in blender: {e}") pass @@ -57,11 +57,11 @@ class Blender(BaseRenderEngine): raise Exception("Uncaught exception") @classmethod - def get_scene_info(cls, project_path): + def get_scene_info(cls, project_path, timeout=10): scene_info = None try: results = cls.run_python_script(project_path, os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'scripts', 'get_blender_info.py')) + 'scripts', 'get_blender_info.py'), timeout=timeout) result_text = results.stdout.decode() for line in result_text.splitlines(): if line.startswith('SCENE_DATA:'): @@ -73,16 +73,17 @@ class Blender(BaseRenderEngine): return scene_info @classmethod - def pack_project_file(cls, project_path): + def pack_project_file(cls, project_path, timeout=30): # Credit to L0Lock for pack script - https://blender.stackexchange.com/a/243935 pack_expression = "import bpy\n" \ "bpy.ops.file.pack_all()\n" \ + "bpy.ops.file.make_paths_absolute()\n" \ "myPath = bpy.data.filepath\n" \ "myPath = str(myPath)\n" \ "bpy.ops.wm.save_as_mainfile(filepath=myPath[:-6]+'_packed'+myPath[-6:])" try: - results = Blender.run_python_expression(project_path, pack_expression) + results = Blender.run_python_expression(project_path, pack_expression, timeout=timeout) result_text = results.stdout.decode() dir_name = os.path.dirname(project_path) diff --git a/lib/render_engines/ffmpeg_engine.py b/lib/render_engines/ffmpeg_engine.py index 2d627b7..a64b4ba 100644 --- a/lib/render_engines/ffmpeg_engine.py +++ b/lib/render_engines/ffmpeg_engine.py @@ -11,7 +11,8 @@ class FFMPEG(BaseRenderEngine): def version(cls): version = None try: - ver_out = subprocess.check_output([cls.renderer_path(), '-version']).decode('utf-8') + ver_out = subprocess.check_output([cls.renderer_path(), '-version'], + timeout=SUBPROCESS_TIMEOUT).decode('utf-8') match = re.match(".*version\s*(\S+)\s*Copyright", ver_out) if match: version = match.groups()[0] @@ -21,14 +22,16 @@ class FFMPEG(BaseRenderEngine): @classmethod def get_encoders(cls): - encoders_raw = subprocess.check_output([cls.renderer_path(), '-encoders'], stderr=subprocess.DEVNULL).decode('utf-8') + encoders_raw = subprocess.check_output([cls.renderer_path(), '-encoders'], stderr=subprocess.DEVNULL, + timeout=SUBPROCESS_TIMEOUT).decode('utf-8') pattern = '(?P[VASFXBD.]{6})\s+(?P\S{2,})\s+(?P.*)' encoders = [m.groupdict() for m in re.finditer(pattern, encoders_raw)] return encoders @classmethod def get_all_formats(cls): - formats_raw = subprocess.check_output([cls.renderer_path(), '-formats'], stderr=subprocess.DEVNULL).decode('utf-8') + formats_raw = subprocess.check_output([cls.renderer_path(), '-formats'], stderr=subprocess.DEVNULL, + timeout=SUBPROCESS_TIMEOUT).decode('utf-8') pattern = '(?P[DE]{1,2})\s+(?P\S{2,})\s+(?P.*)' formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)] return formats