Refactor: Move all initialization logic out of api_server and into init (#91)

* Zeroconf logging improvements

* Ignore RuntimeErrors in background threads - Prevents issues during shutdown

* Migrate start up code from api_server.py to init.py

* Add error handlers to the API server to handle detached instances

* Integrate RenderQueue eval loop into RenderQueue object

* Silently catch RuntimeErrors on evaluate_queue

* Stop background queue updates in prepare_for_shutdown
This commit is contained in:
2024-08-08 04:47:22 -05:00
committed by GitHub
parent 6afb6e65a6
commit 3600eeb21b
8 changed files with 290 additions and 177 deletions

View File

@@ -1,13 +1,15 @@
import logging
import os
import threading
import time
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.exc import DetachedInstanceError
from src.utilities.status_utils import RenderStatus
from src.engines.engine_manager import EngineManager
from src.engines.core.base_worker import Base
from src.utilities.status_utils import RenderStatus
logger = logging.getLogger()
@@ -17,6 +19,9 @@ class JobNotFoundError(Exception):
super().__init__(args)
self.job_id = job_id
def __str__(self):
return f"Cannot find job with ID: {self.job_id}"
class RenderQueue:
engine = None
@@ -24,9 +29,36 @@ class RenderQueue:
job_queue = []
maximum_renderer_instances = {'blender': 1, 'aerender': 1, 'ffmpeg': 4}
last_saved_counts = {}
is_running = False
__eval_thread = None
evaluation_inverval = 1
def __init__(self):
pass
# --------------------------------------------
# Start / Stop Background Updates
# --------------------------------------------
@classmethod
def start(cls):
cls.is_running = True
cls.__eval_thread = threading.Thread(target=cls.__eval_loop, daemon=True)
cls.__eval_thread.start()
@classmethod
def __eval_loop(cls):
while cls.is_running:
try:
RenderQueue.evaluate_queue()
except Exception as e:
logger.exception(f"Uncaught error while evaluating queue: {e}")
time.sleep(cls.evaluation_inverval)
@classmethod
def stop(cls):
cls.is_running = False
# --------------------------------------------
# Queue Management
# --------------------------------------------
@classmethod
def add_to_render_queue(cls, render_job, force_start=False):
@@ -89,6 +121,7 @@ class RenderQueue:
@classmethod
def prepare_for_shutdown(cls):
logger.debug("Closing session")
cls.stop()
running_jobs = cls.jobs_with_status(RenderStatus.RUNNING) # cancel all running jobs
[cls.cancel_job(job) for job in running_jobs]
cls.save_state()
@@ -105,19 +138,22 @@ class RenderQueue:
@classmethod
def evaluate_queue(cls):
not_started = cls.jobs_with_status(RenderStatus.NOT_STARTED, priority_sorted=True)
for job in not_started:
if cls.is_available_for_job(job.renderer, job.priority):
cls.start_job(job)
try:
not_started = cls.jobs_with_status(RenderStatus.NOT_STARTED, priority_sorted=True)
for job in not_started:
if cls.is_available_for_job(job.renderer, job.priority):
cls.start_job(job)
scheduled = cls.jobs_with_status(RenderStatus.SCHEDULED, priority_sorted=True)
for job in scheduled:
if job.scheduled_start <= datetime.now():
logger.debug(f"Starting scheduled job: {job}")
cls.start_job(job)
scheduled = cls.jobs_with_status(RenderStatus.SCHEDULED, priority_sorted=True)
for job in scheduled:
if job.scheduled_start <= datetime.now():
logger.debug(f"Starting scheduled job: {job}")
cls.start_job(job)
if cls.last_saved_counts != cls.job_counts():
cls.save_state()
if cls.last_saved_counts != cls.job_counts():
cls.save_state()
except DetachedInstanceError:
pass
@classmethod
def start_job(cls, job):