Explorar el Código

fix: collecte parallèle des destinations et timeout connect/read séparés

- La vue globale collecte maintenant toutes les destinations (local, SSH,
  instances) en parallèle via ThreadPoolExecutor : une instance lente ou
  hors ligne ne bloque plus les autres
- FederationClient : timeout séparé (5s connect, 15s read) pour détecter
  rapidement les serveurs injoignables sans pénaliser les lectures lentes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cedric Hansen hace 2 semanas
padre
commit
f60a428b24
Se han modificado 2 ficheros con 36 adiciones y 45 borrados
  1. 35 44
      sources/blueprints/overview.py
  2. 1 1
      sources/federation/client.py

+ 35 - 44
sources/blueprints/overview.py

@@ -2,6 +2,7 @@ import json
 import os
 import shlex
 import subprocess
+from concurrent.futures import ThreadPoolExecutor
 
 from flask import Blueprint, current_app, flash, redirect, render_template, request, url_for
 
@@ -128,55 +129,45 @@ def overview():
     instance = current_app.config["INSTANCE_NAME"]
     jobs = Job.query.order_by(Job.name).all()
 
-    locations = []
-
-    # --- Local ---
-    try:
-        archives = _collect_local(backup_dir)
-        groups, orphaned = _group_archives(archives, jobs, instance)
-        locations.append({
-            "type": "local", "label": "Local",
-            "error": None, "groups": groups, "orphaned": orphaned,
-        })
-    except Exception as exc:
-        locations.append({
-            "type": "local", "label": "Local",
-            "error": str(exc), "groups": [], "orphaned": [],
-        })
+    # Construire la liste des tâches de collecte dans l'ordre d'affichage souhaité
+    dests = Destination.query.filter_by(enabled=True).order_by(Destination.name).all()
+    insts = RemoteInstance.query.order_by(RemoteInstance.name).all()
 
-    # --- SSH ---
-    for dest in Destination.query.filter_by(enabled=True).order_by(Destination.name).all():
+    tasks = [
+        ({"type": "local", "label": "Local"},
+         lambda: _collect_local(backup_dir)),
+    ]
+    for dest in dests:
+        tasks.append((
+            {"type": "ssh", "label": f"SSH → {dest.remote_str}", "dest_id": dest.id},
+            lambda d=dest: _collect_ssh(d, data_dir),
+        ))
+    for inst in insts:
+        tasks.append((
+            {"type": "instance", "label": inst.name,
+             "instance_id": inst.id, "instance_url": inst.url},
+            lambda i=inst: _collect_instance(i),
+        ))
+
+    # Collecter toutes les destinations en parallèle pour éviter que les timeouts
+    # d'une destination lente ne bloquent les autres
+    def _run_task(args):
+        meta, fn = args
         try:
-            archives = _collect_ssh(dest, data_dir)
-            groups, orphaned = _group_archives(archives, jobs, instance)
-            locations.append({
-                "type": "ssh", "label": f"SSH → {dest.remote_str}",
-                "dest_id": dest.id, "error": None,
-                "groups": groups, "orphaned": orphaned,
-            })
+            return meta, fn(), None
         except Exception as exc:
-            locations.append({
-                "type": "ssh", "label": f"SSH → {dest.remote_str}",
-                "dest_id": dest.id, "error": str(exc),
-                "groups": [], "orphaned": [],
-            })
+            return meta, None, str(exc)
 
-    # --- Instances fédérées ---
-    for inst in RemoteInstance.query.order_by(RemoteInstance.name).all():
-        try:
-            archives = _collect_instance(inst)
+    with ThreadPoolExecutor(max_workers=min(len(tasks), 10)) as pool:
+        raw_results = list(pool.map(_run_task, tasks))
+
+    locations = []
+    for meta, archives, error in raw_results:
+        if error:
+            locations.append({**meta, "error": error, "groups": [], "orphaned": []})
+        else:
             groups, orphaned = _group_archives(archives, jobs, instance)
-            locations.append({
-                "type": "instance", "label": f"{inst.name}",
-                "instance_id": inst.id, "instance_url": inst.url,
-                "error": None, "groups": groups, "orphaned": orphaned,
-            })
-        except Exception as exc:
-            locations.append({
-                "type": "instance", "label": f"{inst.name}",
-                "instance_id": inst.id, "instance_url": inst.url,
-                "error": str(exc), "groups": [], "orphaned": [],
-            })
+            locations.append({**meta, "error": None, "groups": groups, "orphaned": orphaned})
 
     # --- Vue par job : pivot de locations → jobs ---
     jobs_view = []

+ 1 - 1
sources/federation/client.py

@@ -10,7 +10,7 @@ class FederationClient:
     def __init__(self, instance):
         self.base = instance.url.rstrip("/")
         self.headers = {"X-BackupManager-Key": instance.api_key}
-        self.timeout = 15
+        self.timeout = (5, 15)  # (connect_timeout, read_timeout)
 
     def _get(self, path):
         r = requests.get(f"{self.base}{path}", headers=self.headers,