Add timeouts to engine processes

This commit is contained in:
Brett Williams
2023-05-30 12:09:09 -05:00
parent a668fa70e5
commit 9cebcb3c62
5 changed files with 22 additions and 15 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
*.pyc *.pyc
/server_state.json /server_state.json
/.scheduler_prefs /.scheduler_prefs
/database.db

View File

@@ -14,7 +14,7 @@ class AERender(BaseRenderEngine):
try: try:
render_path = cls.renderer_path() render_path = cls.renderer_path()
if render_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() version = ver_out.decode('utf-8').split(" ")[-1].strip()
except Exception as e: except Exception as e:
logger.error(f'Failed to get {cls.name()} version: {e}') logger.error(f'Failed to get {cls.name()} version: {e}')

View File

@@ -3,6 +3,7 @@ import os
import subprocess import subprocess
logger = logging.getLogger() logger = logging.getLogger()
SUBPROCESS_TIMEOUT = 5
class BaseRenderEngine(object): class BaseRenderEngine(object):
@@ -18,7 +19,7 @@ class BaseRenderEngine(object):
def renderer_path(cls): def renderer_path(cls):
path = None path = None
try: 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: except subprocess.CalledProcessError:
for p in cls.install_paths: for p in cls.install_paths:
if os.path.exists(p): if os.path.exists(p):
@@ -36,7 +37,8 @@ class BaseRenderEngine(object):
path = cls.renderer_path() path = cls.renderer_path()
if not path: if not path:
raise FileNotFoundError("renderer path not found") 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 return help_doc
@classmethod @classmethod

View File

@@ -17,7 +17,7 @@ class Blender(BaseRenderEngine):
try: try:
render_path = cls.renderer_path() render_path = cls.renderer_path()
if render_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() version = ver_out.decode('utf-8').splitlines()[0].replace('Blender', '').strip()
except Exception as e: except Exception as e:
logger.error(f'Failed to get Blender version: {e}') logger.error(f'Failed to get Blender version: {e}')
@@ -30,11 +30,11 @@ class Blender(BaseRenderEngine):
return formats return formats
@classmethod @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): if os.path.exists(project_path):
try: try:
return subprocess.run([cls.renderer_path(), '-b', project_path, '--python-expr', python_expression], 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: except Exception as e:
logger.warning(f"Error running python expression in blender: {e}") logger.warning(f"Error running python expression in blender: {e}")
pass pass
@@ -42,11 +42,11 @@ class Blender(BaseRenderEngine):
raise FileNotFoundError(f'Project file not found: {project_path}') raise FileNotFoundError(f'Project file not found: {project_path}')
@classmethod @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): if os.path.exists(project_path) and os.path.exists(script_path):
try: try:
return subprocess.run([cls.renderer_path(), '-b', project_path, '--python', script_path], return subprocess.run([cls.renderer_path(), '-b', project_path, '--python', script_path],
capture_output=True) capture_output=True, timeout=timeout)
except Exception as e: except Exception as e:
logger.warning(f"Error running python expression in blender: {e}") logger.warning(f"Error running python expression in blender: {e}")
pass pass
@@ -57,11 +57,11 @@ class Blender(BaseRenderEngine):
raise Exception("Uncaught exception") raise Exception("Uncaught exception")
@classmethod @classmethod
def get_scene_info(cls, project_path): def get_scene_info(cls, project_path, timeout=10):
scene_info = None scene_info = None
try: try:
results = cls.run_python_script(project_path, os.path.join(os.path.dirname(os.path.realpath(__file__)), 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() result_text = results.stdout.decode()
for line in result_text.splitlines(): for line in result_text.splitlines():
if line.startswith('SCENE_DATA:'): if line.startswith('SCENE_DATA:'):
@@ -73,16 +73,17 @@ class Blender(BaseRenderEngine):
return scene_info return scene_info
@classmethod @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 # Credit to L0Lock for pack script - https://blender.stackexchange.com/a/243935
pack_expression = "import bpy\n" \ pack_expression = "import bpy\n" \
"bpy.ops.file.pack_all()\n" \ "bpy.ops.file.pack_all()\n" \
"bpy.ops.file.make_paths_absolute()\n" \
"myPath = bpy.data.filepath\n" \ "myPath = bpy.data.filepath\n" \
"myPath = str(myPath)\n" \ "myPath = str(myPath)\n" \
"bpy.ops.wm.save_as_mainfile(filepath=myPath[:-6]+'_packed'+myPath[-6:])" "bpy.ops.wm.save_as_mainfile(filepath=myPath[:-6]+'_packed'+myPath[-6:])"
try: 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() result_text = results.stdout.decode()
dir_name = os.path.dirname(project_path) dir_name = os.path.dirname(project_path)

View File

@@ -11,7 +11,8 @@ class FFMPEG(BaseRenderEngine):
def version(cls): def version(cls):
version = None version = None
try: 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) match = re.match(".*version\s*(\S+)\s*Copyright", ver_out)
if match: if match:
version = match.groups()[0] version = match.groups()[0]
@@ -21,14 +22,16 @@ class FFMPEG(BaseRenderEngine):
@classmethod @classmethod
def get_encoders(cls): 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<type>[VASFXBD.]{6})\s+(?P<name>\S{2,})\s+(?P<description>.*)' pattern = '(?P<type>[VASFXBD.]{6})\s+(?P<name>\S{2,})\s+(?P<description>.*)'
encoders = [m.groupdict() for m in re.finditer(pattern, encoders_raw)] encoders = [m.groupdict() for m in re.finditer(pattern, encoders_raw)]
return encoders return encoders
@classmethod @classmethod
def get_all_formats(cls): 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<type>[DE]{1,2})\s+(?P<name>\S{2,})\s+(?P<description>.*)' pattern = '(?P<type>[DE]{1,2})\s+(?P<name>\S{2,})\s+(?P<description>.*)'
formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)] formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)]
return formats return formats