poller fixes per profil

This commit is contained in:
Mateusz Gruszczyński
2026-06-20 18:39:00 +02:00
parent e1b5822a59
commit d4c9150c42
7 changed files with 129 additions and 35 deletions
+47 -7
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import json
import threading
import time
import psutil
from datetime import datetime, timezone
@@ -46,6 +47,29 @@ _LAST_RUN: dict[int, float] = {}
_LAST_LIMITS: dict[int, tuple[int, int]] = {}
_HIGH_CPU_SINCE: dict[int, float] = {}
_PLANNER_CONNECTION_STATUS: dict[int, str] = {}
_SCHEDULER_STARTED = False
_SCHEDULER_LOCK = threading.Lock()
_PROFILE_LOCKS: dict[int, threading.Lock] = {}
_PROFILE_LOCKS_GUARD = threading.Lock()
def _profile_lock(profile_id: int) -> threading.Lock:
"""Keep one planner run per profile active at a time."""
with _PROFILE_LOCKS_GUARD:
if profile_id not in _PROFILE_LOCKS:
_PROFILE_LOCKS[profile_id] = threading.Lock()
return _PROFILE_LOCKS[profile_id]
def _all_profiles() -> list[dict]:
"""Read every configured profile directly from DB for browser-independent background work."""
with connect() as conn:
return [dict(row) for row in conn.execute("SELECT * FROM rtorrent_profiles ORDER BY id").fetchall()]
def _owner_user_id(profile: dict) -> int:
"""Use the profile owner for background planner checks."""
return int(profile.get("user_id") or default_user_id())
def _rtorrent_ready(profile: dict) -> tuple[bool, str]:
@@ -580,26 +604,42 @@ def preview(profile: dict, user_id: int | None = None) -> dict:
def start_scheduler(socketio=None) -> None:
"""Start the browser-independent planner loop for every configured profile."""
global _SCHEDULER_STARTED
with _SCHEDULER_LOCK:
if _SCHEDULER_STARTED:
return
_SCHEDULER_STARTED = True
def loop():
while True:
try:
from .websocket import emit_profile_event
profiles: list[dict]
with connect() as conn:
profiles = [dict(row) for row in conn.execute("SELECT * FROM rtorrent_profiles ORDER BY id").fetchall()]
for profile in profiles:
for profile in _all_profiles():
profile_id = int(profile.get("id") or 0)
if not profile_id:
continue
lock = _profile_lock(profile_id)
if not lock.acquire(blocking=False):
continue
try:
result = enforce(profile, force=False)
# Note: Background planner runs per configured profile with the profile owner, not only for the active UI profile.
result = enforce(profile, force=False, user_id=_owner_user_id(profile))
if socketio and result.get("enabled") and not result.get("skipped"):
emit_profile_event(socketio, "download_plan_update", result, int(profile["id"]))
emit_profile_event(socketio, "download_plan_update", result, profile_id)
except Exception as exc:
if socketio:
emit_profile_event(socketio, "download_plan_update", {"ok": False, "profile_id": int(profile.get("id") or 0), "error": str(exc)}, int(profile.get("id") or 0))
emit_profile_event(socketio, "download_plan_update", {"ok": False, "profile_id": profile_id, "error": str(exc)}, profile_id)
finally:
lock.release()
except Exception:
pass
if socketio:
socketio.sleep(30)
else:
time.sleep(30)
if socketio:
socketio.start_background_task(loop)
else:
threading.Thread(target=loop, daemon=True, name="pytorrent-download-planner-scheduler").start()