diff --git a/srv/tesm-license/VERSION b/srv/tesm-license/VERSION index 26aaba0..6085e94 100644 --- a/srv/tesm-license/VERSION +++ b/srv/tesm-license/VERSION @@ -1 +1 @@ -1.2.0 +1.2.1 diff --git a/srv/tesm-license/app.py b/srv/tesm-license/app.py index 0454f3e..3681c3c 100644 --- a/srv/tesm-license/app.py +++ b/srv/tesm-license/app.py @@ -580,7 +580,14 @@ class User(UserMixin): def get_db_connection(): - conn = sqlite3.connect(DB_PATH) + # timeout=15 statt Python-Default 5s: reine Verteidigungslinie gegen + # kurze, legitime Ueberschneidungen zweier Schreibzugriffe (SQLite + # erlaubt nur EINEN Schreiber gleichzeitig) -- ersetzt NICHT die + # eigentliche Regel, niemals eine zweite Verbindung waehrend einer noch + # offenen Schreibtransaktion auf derselben zu committen (siehe + # _process_activate für ein Beispiel, wo genau das den Fehler + # "database is locked" ausgeloest hat). + conn = sqlite3.connect(DB_PATH, timeout=15) conn.row_factory = sqlite3.Row return conn @@ -2202,19 +2209,33 @@ def _process_activate(data): "SELECT license_id, customer_id FROM licenses WHERE fingerprint=? AND status='active' AND license_id!=?", (fingerprint, license_id), ).fetchall() + # log_action_system() oeffnet INTERN eine EIGENE, zweite Verbindung und + # committet sofort -- solange `conn` hier noch eine offene Schreib- + # transaktion haelt (erstes UPDATE oben, noch nicht committet), wuerde + # dieser zweite Commit-Versuch auf sich selbst warten (SQLite erlaubt + # nur EINEN Schreiber gleichzeitig) und nach Ablauf des Busy-Timeouts + # mit "database is locked" fehlschlagen -- live reproduziert beim + # Aktivieren einer Lizenz, die eine andere fuer denselben Fingerprint + # ersetzt. Deshalb werden die Log-Eintraege hier nur VORGEMERKT (reine + # Lesezugriffe auf `conn` sind unproblematisch, nur der schreibende + # log_action_system()-Aufruf selbst muss warten) und dann als allerletzter + # Schritt nach `conn.commit()`/`conn.close()` unten nachgeholt. + superseded_logs = [] for old in superseded: conn.execute( "UPDATE licenses SET status='deactivated', deactivated_at=? WHERE license_id=?", (now, old["license_id"]), ) old_customer = _customer_row(conn, old["customer_id"]) - log_action_system( - "license.superseded", old_customer["name"] if old_customer else old["license_id"], + superseded_logs.append(( + old_customer["name"] if old_customer else old["license_id"], f"Automatisch deaktiviert -- Fingerprint {fingerprint} hat jetzt Lizenz {license_id} aktiviert.", - ) + )) conn.commit() customer = _customer_row(conn, row["customer_id"]) conn.close() + for target, details in superseded_logs: + log_action_system("license.superseded", target, details) log_action_system("license.activated_remote", customer["name"] if customer else license_id, f"Fingerprint {fingerprint}") return licensing.build_master_response("activated", license_id, fingerprint, MASTER_PRIVATE_KEY, status="ok"), 200