diff --git a/.env-sample b/.env-sample index 177b4ee..a28a6d6 100644 --- a/.env-sample +++ b/.env-sample @@ -27,6 +27,7 @@ FASTAPI_PORT=8000 FASTAPI_WORKERS=1 # DO NOT change this if you don't know what you are doing. Setting this to -1 will spawn a worker for each CPU core, which can consume several GBs of RAM and cause high CPU usage. USE_GUNICORN=True # Will use uvicorn if False or if on Windows GUNICORN_PRELOAD_APP=True # Set to False to start workers without preloading the app (reduces startup cost but requires schema to exist) +EXECUTOR_MAX_WORKERS= # Max workers for ProcessPoolExecutor (handles CPU-intensive tasks like RTN parsing). Leave empty for auto (min(cpu_count, 4)). Increase for higher concurrency on CPU-bound workloads. # ============================== # # Playback Settings # diff --git a/comet/core/execution.py b/comet/core/execution.py index 52821f9..d4f77c0 100644 --- a/comet/core/execution.py +++ b/comet/core/execution.py @@ -4,6 +4,8 @@ import os import signal from concurrent.futures import ProcessPoolExecutor +from comet.core.models import settings + _mp_context = None try: _mp_context = multiprocessing.get_context("forkserver") @@ -11,19 +13,19 @@ except ValueError: _mp_context = multiprocessing.get_context("spawn") app_executor = None +max_workers = settings.EXECUTOR_MAX_WORKERS +if max_workers is None: + cpu_count = os.cpu_count() or 1 + max_workers = min(cpu_count, 4) def worker_initializer(): signal.signal(signal.SIGINT, signal.SIG_IGN) -def setup_executor(max_workers: int | None = None): +def setup_executor(): global app_executor - if max_workers is None: - cpu_count = os.cpu_count() or 1 - max_workers = min(cpu_count, 4) - app_executor = ProcessPoolExecutor( max_workers=max_workers, mp_context=_mp_context, initializer=worker_initializer ) diff --git a/comet/core/logger.py b/comet/core/logger.py index 0bbca1e..0a6cbee 100644 --- a/comet/core/logger.py +++ b/comet/core/logger.py @@ -5,6 +5,7 @@ import time from loguru import logger +from comet.core.execution import max_workers from comet.core.log_levels import (CUSTOM_LOG_LEVELS, STANDARD_LOG_LEVELS, get_level_info) @@ -176,6 +177,11 @@ def log_startup_info(settings): ) logger.log("COMET", f"Gunicorn Preload App: {settings.GUNICORN_PRELOAD_APP}") + logger.log( + "COMET", + f"ProcessPoolExecutor: {max_workers} workers {'(auto)' if settings.EXECUTOR_MAX_WORKERS is None else ''}", + ) + if settings.PUBLIC_BASE_URL: logger.log("COMET", f"Public Base URL: {settings.PUBLIC_BASE_URL}") diff --git a/comet/core/models.py b/comet/core/models.py index cdd5cf6..b7b5a29 100644 --- a/comet/core/models.py +++ b/comet/core/models.py @@ -28,6 +28,7 @@ class AppSettings(BaseSettings): FASTAPI_WORKERS: Optional[int] = 1 USE_GUNICORN: Optional[bool] = True GUNICORN_PRELOAD_APP: Optional[bool] = True + EXECUTOR_MAX_WORKERS: Optional[int] = None ADMIN_DASHBOARD_PASSWORD: Optional[str] = "".join( random.choices(string.ascii_letters + string.digits, k=16) )