8fa36b774e
Key changes: - New: data_loader.py SQLite persistence with drop_sqlite_table - New: db_utils.py retry_on_lock decorator (3 retries, exponential backoff) - New: tool_registry.py with 27 MCP tools (filter_and_cluster, compute_scores, etc.) - New: tls_ref.py for TLS cipher/reference data - New: import_tlsdb.py management command - New: scripts/start_server.py for portable runtime - New: migrations 0003-0007 for SQLite table, display_id, llm fields - Changed: views.py unified pipeline worker, retry_run, display_id everywhere - Changed: models.py with display_id auto-assignment, run_type, sqlite_table, llm_thinking, tool_calls_json - Changed: urls.py added retry_run route - Changed: session_store.py robust JSON persistence - Changed: AGENTS.md v7 fix summary added - Changed: templates — globe rewrite (inertia/polar flip), auto.html (thinking/tool accordions), base.html (toast/config), all pages use display_id - Changed: run.bat PYTHONUTF8=1 - Deleted: entity_detector.py, entity_aggregator.py (replaced by filter_and_cluster clustering pipeline) - Test: 92/92 unit tests passing
29 lines
1.0 KiB
Python
29 lines
1.0 KiB
Python
"""Database utilities for concurrent access."""
|
|
import functools
|
|
import time
|
|
import logging
|
|
from django.db import OperationalError, close_old_connections
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def retry_on_lock(max_retries=3, base_delay=0.5):
|
|
"""Decorator: retry on OperationalError('database is locked')."""
|
|
def decorator(func):
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
for attempt in range(max_retries):
|
|
close_old_connections()
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except OperationalError as e:
|
|
if 'database is locked' in str(e) and attempt < max_retries - 1:
|
|
delay = base_delay * (2 ** attempt)
|
|
logger.warning(f'[DB_LOCKED] retry {attempt+1}/{max_retries} in {delay:.1f}s')
|
|
time.sleep(delay)
|
|
close_old_connections()
|
|
else:
|
|
raise
|
|
return wrapper
|
|
return decorator
|