From 9a7f446637e49ed8f8e16dbf69eb994db091944c Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Sat, 25 Apr 2026 00:07:22 +0000 Subject: [PATCH] fix(qualys/dashboard): insert pending row dans la route avant spawn thread (no race) --- app/routers/qualys.py | 11 ++++++++--- app/services/qualys_service.py | 18 +++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/routers/qualys.py b/app/routers/qualys.py index dacd61f..8050d20 100644 --- a/app/routers/qualys.py +++ b/app/routers/qualys.py @@ -1219,15 +1219,20 @@ async def qualys_dashboard_refresh(request: Request, db=Depends(get_db)): WHERE status='pending' AND run_at > now() - interval '15 minutes' LIMIT 1""")).fetchone() if pending: return RedirectResponse(url="/qualys/dashboard?msg=already_running", status_code=303) + # Insert pending immediatement (route, pas thread) pour eviter race condition + run_id = db.execute(text("""INSERT INTO qualys_vuln_snapshot_run (status, triggered_by) + VALUES ('pending', :tb) RETURNING id"""), + {"tb": f"manual:{user.username}"}).scalar() + db.commit() import threading - def _runner(): + def _runner(rid): from app.database import SessionLocal s = SessionLocal() try: - compute_vuln_dashboard(s, triggered_by=f"manual:{user.username}") + compute_vuln_dashboard(s, triggered_by=f"manual:{user.username}", run_id=rid) finally: s.close() - threading.Thread(target=_runner, daemon=True).start() + threading.Thread(target=_runner, args=(run_id,), daemon=True).start() return RedirectResponse(url="/qualys/dashboard?msg=refresh_started", status_code=303) diff --git a/app/services/qualys_service.py b/app/services/qualys_service.py index e75d4cf..4cc1a25 100644 --- a/app/services/qualys_service.py +++ b/app/services/qualys_service.py @@ -889,8 +889,8 @@ def _is_scanned(asset_row, has_vuln_data): return True return False -def compute_vuln_dashboard(db, triggered_by="manual"): - """Calcule un nouveau snapshot (insert run + snapshot rows). +def compute_vuln_dashboard(db, triggered_by="manual", run_id=None): + """Calcule un nouveau snapshot. Si run_id fourni, l'utilise (sinon en cree un). Retourne dict {ok, msg, run_id, asset_count, duration_sec}.""" global _dashboard_running if _dashboard_running: @@ -899,14 +899,14 @@ def compute_vuln_dashboard(db, triggered_by="manual"): _dashboard_running = True import time t0 = time.time() - run_id = None try: - # 1. Creer le run en pending - run_id = db.execute(text(""" - INSERT INTO qualys_vuln_snapshot_run (status, triggered_by) - VALUES ('pending', :tb) RETURNING id - """), {"tb": triggered_by}).scalar() - db.commit() + # 1. Creer le run en pending si pas deja fourni + if run_id is None: + run_id = db.execute(text(""" + INSERT INTO qualys_vuln_snapshot_run (status, triggered_by) + VALUES ('pending', :tb) RETURNING id + """), {"tb": triggered_by}).scalar() + db.commit() # 2. Charger tous les assets avec leurs tags + domaine AD rows = db.execute(text("""