Files
Nat20-Notes/backend/app/jobs.py
KansaiGaijin bc7ac32de3 Add campaign layer, GPU model caching, file browser cleanup
- Campaigns: new table, CRUD API, React context + provider
- Sessions: scoped to campaigns, paths under campaigns/{id}/
- File browser: scoped per campaign, removed copy/paste/autoPlay
- Sidebar: campaign selector dropdown at top
- Transcribe: GPU model cached/released via job counter
- Jobs: status text updates dynamically in real-time
- Auto-redirect: blocked when summarize job is active
2026-07-10 21:41:29 +12:00

68 lines
2.2 KiB
Python

"""
Minimal background job runner.
GPU jobs use a shared counter so the Whisper model stays cached between
back-to-back transcriptions and is only released when no more GPU work
is queued.
"""
import threading
import traceback
from concurrent.futures import ThreadPoolExecutor
from . import database as db
from .errors import PipelineError
from .logging_config import get_logger
log = get_logger(__name__)
_executor = ThreadPoolExecutor(max_workers=1)
_gpu_jobs_queued = 0
_gpu_lock = threading.Lock()
def _unload_gpu():
"""Release the cached Whisper model from GPU memory."""
from .pipeline.transcribe import unload_models
unload_models()
def submit(job_id: str, fn, *args, requires_gpu=False, **kwargs):
if requires_gpu:
with _gpu_lock:
global _gpu_jobs_queued
_gpu_jobs_queued += 1
def _run():
log.info("Job %s starting", job_id)
db.update_job(job_id, status="running", progress="Starting...")
try:
def progress_cb(msg):
log.info("Job %s: %s", job_id, msg)
db.update_job(job_id, progress=msg)
fn(*args, progress_cb=progress_cb, **kwargs)
db.update_job(job_id, status="done", progress="Complete")
log.info("Job %s complete", job_id)
except PipelineError as e:
log.error("Job %s failed at stage '%s': %s", job_id, e.stage, e.message)
db.update_job(
job_id, status="error",
error=e.message, error_stage=e.stage,
error_detail=traceback.format_exc(),
)
except Exception as e:
log.exception("Job %s failed with unexpected error", job_id)
db.update_job(
job_id, status="error",
error=f"Unexpected error: {e}", error_stage="unknown",
error_detail=traceback.format_exc(),
)
finally:
if requires_gpu:
with _gpu_lock:
global _gpu_jobs_queued
_gpu_jobs_queued -= 1
if _gpu_jobs_queued <= 0:
log.info("No more GPU jobs queued — unloading Whisper model")
_unload_gpu()
_executor.submit(_run)