fix: Threading-Lock verhindert parallele Scans, Status-API, Button-Feedback
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ba0893a337
commit
639e6a4fc3
2 changed files with 103 additions and 36 deletions
|
|
@ -7,6 +7,10 @@ from datetime import datetime, timedelta
|
|||
from db import init_db, get_conn, log
|
||||
from ki import auswerten
|
||||
|
||||
# Verhindert dass zwei Läufe gleichzeitig laufen
|
||||
_scan_lock = threading.Lock()
|
||||
_lauf_aktiv = False
|
||||
|
||||
|
||||
def get_nodes():
|
||||
conn = get_conn()
|
||||
|
|
@ -105,55 +109,88 @@ def speichere_preise(results, node_name, job):
|
|||
def scraping_lauf(label="Standard", flex_tage_liste=None):
|
||||
"""
|
||||
Führt alle aktiven Jobs auf allen Nodes aus.
|
||||
flex_tage_liste: Liste von 'tage'-Werten für Datums-Flexibilität (z.B. [27,28,30,32,33])
|
||||
Übersprungen wenn ein anderer Lauf noch aktiv ist (Lock-Schutz).
|
||||
"""
|
||||
log(f"=== Scraping-Lauf [{label}] gestartet ===")
|
||||
nodes = get_nodes()
|
||||
jobs = get_aktive_jobs()
|
||||
global _lauf_aktiv
|
||||
|
||||
if not nodes:
|
||||
log("Keine aktiven Nodes konfiguriert", "WARN")
|
||||
if not _scan_lock.acquire(blocking=False):
|
||||
log(f"Lauf [{label}] übersprungen — vorheriger Lauf noch aktiv", "WARN")
|
||||
return
|
||||
|
||||
tage_varianten = flex_tage_liste or [None] # None = Job-Default verwenden
|
||||
_lauf_aktiv = True
|
||||
start = datetime.now()
|
||||
|
||||
online = 0
|
||||
for node in nodes:
|
||||
if node_ping(node):
|
||||
update_node_status(node["name"], "online")
|
||||
online += 1
|
||||
for job in jobs:
|
||||
for tage_var in tage_varianten:
|
||||
dispatch_job(node, job, tage_override=tage_var)
|
||||
else:
|
||||
log(f"Node {node['name']} nicht erreichbar", "WARN")
|
||||
update_node_status(node["name"], "offline")
|
||||
try:
|
||||
log(f"=== Scraping-Lauf [{label}] gestartet ===")
|
||||
nodes = get_nodes()
|
||||
jobs = get_aktive_jobs()
|
||||
|
||||
log(f"Scraping [{label}] abgeschlossen — {online}/{len(nodes)} Nodes online")
|
||||
auswerten()
|
||||
log(f"=== Lauf [{label}] beendet ===")
|
||||
if not nodes:
|
||||
log("Keine aktiven Nodes konfiguriert", "WARN")
|
||||
return
|
||||
|
||||
tage_varianten = flex_tage_liste or [None]
|
||||
|
||||
online = 0
|
||||
fehler = 0
|
||||
preise_gesamt = 0
|
||||
|
||||
for node in nodes:
|
||||
if node_ping(node):
|
||||
update_node_status(node["name"], "online")
|
||||
online += 1
|
||||
for job in jobs:
|
||||
for tage_var in tage_varianten:
|
||||
try:
|
||||
ok = dispatch_job(node, job, tage_override=tage_var)
|
||||
if not ok:
|
||||
fehler += 1
|
||||
except Exception as e:
|
||||
log(f"Job-Fehler {node['name']}/{job['scanner']}: {e}", "ERROR")
|
||||
fehler += 1
|
||||
else:
|
||||
log(f"Node {node['name']} nicht erreichbar", "WARN")
|
||||
update_node_status(node["name"], "offline")
|
||||
|
||||
dauer = round((datetime.now() - start).total_seconds())
|
||||
log(f"Scraping [{label}] fertig — {online}/{len(nodes)} Nodes | "
|
||||
f"{fehler} Fehler | {dauer}s Laufzeit")
|
||||
|
||||
try:
|
||||
auswerten()
|
||||
except Exception as e:
|
||||
log(f"KI-Fehler: {e}", "ERROR")
|
||||
|
||||
log(f"=== Lauf [{label}] beendet ===")
|
||||
|
||||
finally:
|
||||
_lauf_aktiv = False
|
||||
_scan_lock.release()
|
||||
|
||||
|
||||
def standard_lauf():
|
||||
"""Normaler 4×-täglich Lauf mit Standard-Datum."""
|
||||
scraping_lauf(label=datetime.now().strftime("%H:%M"))
|
||||
"""30-Minuten-Takt — in eigenem Thread damit der Scheduler nicht blockiert."""
|
||||
threading.Thread(
|
||||
target=scraping_lauf,
|
||||
kwargs={"label": datetime.now().strftime("%H:%M")},
|
||||
daemon=True
|
||||
).start()
|
||||
|
||||
|
||||
def flex_lauf():
|
||||
"""
|
||||
Di/Mi Nacht: ±3 Tage Datums-Flexibilität.
|
||||
Sucht Abflug in 27-33 Tagen statt nur 30 Tagen.
|
||||
"""
|
||||
wochentag = datetime.now().weekday() # 0=Mo, 1=Di, 2=Mi
|
||||
"""Di/Mi 23:30 — ±3 Tage Datumsfenster."""
|
||||
wochentag = datetime.now().weekday()
|
||||
if wochentag not in (1, 2):
|
||||
log("Flex-Lauf: heute kein Di/Mi — übersprungen")
|
||||
return
|
||||
|
||||
# 7 Varianten: 30±3 Tage
|
||||
basis = 30
|
||||
flex_varianten = list(range(basis - 3, basis + 4)) # [27,28,29,30,31,32,33]
|
||||
flex_varianten = list(range(basis - 3, basis + 4))
|
||||
log(f"=== Flex-Lauf Di/Mi ±3 Tage: {flex_varianten} ===")
|
||||
scraping_lauf(label="Flex-Di/Mi", flex_tage_liste=flex_varianten)
|
||||
threading.Thread(
|
||||
target=scraping_lauf,
|
||||
kwargs={"label": "Flex-Di/Mi", "flex_tage_liste": flex_varianten},
|
||||
daemon=True
|
||||
).start()
|
||||
|
||||
|
||||
def run():
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ OVERVIEW_HTML = BASE_HTML.replace("{% block content %}{% endblock %}", """
|
|||
<div style="margin-top:0.5rem;font-size:0.75rem;color:#475569" id="ki-datum"></div>
|
||||
</div>
|
||||
<div style="margin-top:1rem">
|
||||
<button class="btn btn-green" onclick="manuellScrapen()">▶ Jetzt scrapen</button>
|
||||
<button id="scan-btn" class="btn btn-green" onclick="manuellScrapen()">▶ Jetzt scrapen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -289,13 +289,34 @@ async function ladeUebersicht() {
|
|||
});
|
||||
}
|
||||
|
||||
async function pruefeScanStatus() {
|
||||
const d = await fetch('/api/scrape/status').then(r=>r.json());
|
||||
const btn = document.getElementById('scan-btn');
|
||||
if (d.running) {
|
||||
btn.textContent = '⏳ Läuft...';
|
||||
btn.disabled = true;
|
||||
btn.style.background = '#92400e';
|
||||
setTimeout(pruefeScanStatus, 5000);
|
||||
} else {
|
||||
btn.textContent = '▶ Jetzt scrapen';
|
||||
btn.disabled = false;
|
||||
btn.style.background = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function manuellScrapen() {
|
||||
if (!confirm('Scraping jetzt starten?')) return;
|
||||
const r = await fetch('/api/scrape/now', {method:'POST'});
|
||||
const d = await r.json();
|
||||
alert(d.message);
|
||||
if (r.status === 409) {
|
||||
alert(d.message);
|
||||
return;
|
||||
}
|
||||
pruefeScanStatus();
|
||||
}
|
||||
|
||||
pruefeScanStatus();
|
||||
setInterval(pruefeScanStatus, 10000);
|
||||
|
||||
ladeUebersicht();
|
||||
</script>
|
||||
""")
|
||||
|
|
@ -491,12 +512,21 @@ def api_schedule():
|
|||
|
||||
@app.route("/api/scrape/now", methods=["POST"])
|
||||
def api_scrape_now():
|
||||
from scheduler import _lauf_aktiv
|
||||
if _lauf_aktiv:
|
||||
return jsonify({"message": "⚠ Lauf bereits aktiv — bitte warten", "running": True}), 409
|
||||
threading.Thread(
|
||||
target=scraping_lauf,
|
||||
kwargs={"label": "Manuell"},
|
||||
daemon=True
|
||||
).start()
|
||||
return jsonify({"message": "Scraping gestartet — läuft im Hintergrund"})
|
||||
return jsonify({"message": "Scraping gestartet — läuft im Hintergrund", "running": False})
|
||||
|
||||
|
||||
@app.route("/api/scrape/status")
|
||||
def api_scrape_status():
|
||||
from scheduler import _lauf_aktiv
|
||||
return jsonify({"running": _lauf_aktiv})
|
||||
|
||||
|
||||
# ─── Seiten ────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue