Pace bulk watchlist refresh requests
This commit is contained in:
+28
-31
@@ -2,7 +2,6 @@
|
||||
|
||||
"""Queue and background job services for ani-cli-web."""
|
||||
|
||||
import concurrent.futures
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -33,7 +32,6 @@ from app_support import (
|
||||
|
||||
LOG_PERSIST_INTERVAL_SECONDS = 0.75
|
||||
LOG_PERSIST_LINE_BATCH = 8
|
||||
WATCHLIST_REFRESH_MAX_WORKERS = 4
|
||||
JOB_STRUCTURED_COLUMNS = (
|
||||
"show_id",
|
||||
"title",
|
||||
@@ -59,8 +57,9 @@ JOB_EXTRA_PAYLOAD_COLUMN = "payload"
|
||||
|
||||
|
||||
class WatchlistRefreshJobs:
|
||||
def __init__(self, store, start_worker=True):
|
||||
def __init__(self, store, config_getter=None, start_worker=True):
|
||||
self.store = store
|
||||
self.config_getter = config_getter or (lambda: {})
|
||||
self.lock = threading.RLock()
|
||||
self.wakeup = threading.Event()
|
||||
self.stop_event = threading.Event()
|
||||
@@ -153,6 +152,14 @@ class WatchlistRefreshJobs:
|
||||
self._upsert_job_conn(conn, job)
|
||||
self._trim_history_conn(conn)
|
||||
|
||||
def _refresh_delay_seconds(self):
|
||||
config = self.config_getter() or {}
|
||||
try:
|
||||
delay = int(str(config.get("watchlist_refresh_delay_seconds", 5)).strip() or "5")
|
||||
except (TypeError, ValueError):
|
||||
delay = 5
|
||||
return max(0, min(300, delay))
|
||||
|
||||
def _load_jobs(self):
|
||||
with self.lock, self._connect() as conn:
|
||||
rows = conn.execute(
|
||||
@@ -257,6 +264,7 @@ class WatchlistRefreshJobs:
|
||||
source_name = str(job.get("source") or "manual").strip().lower() or "manual"
|
||||
try:
|
||||
show_ids = self.store.all_show_ids()
|
||||
delay_seconds = self._refresh_delay_seconds()
|
||||
started_at = now_iso()
|
||||
with self.lock:
|
||||
job["status"] = "running"
|
||||
@@ -271,13 +279,14 @@ class WatchlistRefreshJobs:
|
||||
if source_name == "scheduled":
|
||||
self._stdout_log(f"Scheduled refresh started (job={job['id']}, total={len(show_ids)}).")
|
||||
|
||||
def refresh_one(show_id):
|
||||
for index, show_id in enumerate(show_ids, start=1):
|
||||
if self.stop_event.is_set():
|
||||
interrupted = True
|
||||
break
|
||||
title = show_id
|
||||
errored = False
|
||||
skipped = False
|
||||
error_message = ""
|
||||
if self.stop_event.is_set():
|
||||
return {"title": title, "errored": False, "skipped": True, "error": ""}
|
||||
try:
|
||||
item = self.store.refresh(show_id, include_thumbnail=False)
|
||||
if item is None:
|
||||
@@ -290,34 +299,22 @@ class WatchlistRefreshJobs:
|
||||
errored = True
|
||||
error_message = str(exc)
|
||||
debug_log("watchlist.refresh_all.item_error", show_id=show_id, error=exc)
|
||||
return {"title": title, "errored": errored, "skipped": skipped, "error": error_message}
|
||||
|
||||
max_workers = max(1, min(WATCHLIST_REFRESH_MAX_WORKERS, len(show_ids) or 1))
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
with self.lock:
|
||||
self.current_executor = executor
|
||||
futures = [executor.submit(refresh_one, show_id) for show_id in show_ids]
|
||||
for index, future in enumerate(concurrent.futures.as_completed(futures), start=1):
|
||||
if self.stop_event.is_set():
|
||||
job["completed"] = index
|
||||
job["errors"] += 1 if errored else 0
|
||||
job["current_title"] = title
|
||||
job["updated_at"] = now_iso()
|
||||
if errored and error_message:
|
||||
job["last_error"] = error_message
|
||||
if skipped:
|
||||
job["message"] = f"Refreshing {index}/{len(show_ids)}: removed entry"
|
||||
else:
|
||||
job["message"] = f"Refreshing {index}/{len(show_ids)}: {title}"
|
||||
self._save_job_locked(job)
|
||||
if index < len(show_ids) and delay_seconds > 0:
|
||||
if self.stop_event.wait(delay_seconds):
|
||||
interrupted = True
|
||||
executor.shutdown(wait=False, cancel_futures=True)
|
||||
break
|
||||
result = future.result()
|
||||
title = result["title"]
|
||||
with self.lock:
|
||||
job["completed"] = index
|
||||
job["errors"] += 1 if result["errored"] else 0
|
||||
job["current_title"] = title
|
||||
job["updated_at"] = now_iso()
|
||||
if result["errored"] and result["error"]:
|
||||
job["last_error"] = result["error"]
|
||||
if result["skipped"]:
|
||||
job["message"] = f"Refreshing {index}/{len(show_ids)}: removed entry"
|
||||
else:
|
||||
job["message"] = f"Refreshing {index}/{len(show_ids)}: {title}"
|
||||
self._save_job_locked(job)
|
||||
with self.lock:
|
||||
self.current_executor = None
|
||||
|
||||
finished_at = now_iso()
|
||||
with self.lock:
|
||||
|
||||
Reference in New Issue
Block a user