Fix config save 'database is locked': add 30s timeout, WAL mode, and retry

Settings DB connections had no timeout (default 5s), causing lock failures when
enrichment workers hold concurrent write locks. Added 30s timeout, WAL journal
mode for better concurrency, retry-once before falling back to config.json.
This commit is contained in:
Broque Thomas 2026-03-23 08:54:53 -07:00
parent df390feece
commit a48018f4ca

View file

@ -247,7 +247,8 @@ class ConfigManager:
self.database_path.parent.mkdir(parents=True, exist_ok=True)
# Connect to database (creates file if it doesn't exist)
conn = sqlite3.connect(str(self.database_path))
conn = sqlite3.connect(str(self.database_path), timeout=30.0)
conn.execute("PRAGMA journal_mode=WAL")
cursor = conn.cursor()
# Create metadata table if it doesn't exist
@ -265,14 +266,15 @@ class ConfigManager:
def _load_from_database(self) -> Optional[Dict[str, Any]]:
"""Load configuration from database, decrypting sensitive values."""
conn = None
try:
self._ensure_database_exists()
conn = sqlite3.connect(str(self.database_path))
conn = sqlite3.connect(str(self.database_path), timeout=30.0)
conn.execute("PRAGMA journal_mode=WAL")
cursor = conn.cursor()
cursor.execute("SELECT value FROM metadata WHERE key = 'app_config'")
row = cursor.fetchone()
conn.close()
if row and row[0]:
config_data = json.loads(row[0])
@ -286,16 +288,22 @@ class ConfigManager:
except Exception as e:
print(f"Warning: Could not load config from database: {e}")
return None
finally:
if conn:
conn.close()
def _save_to_database(self, config_data: Dict[str, Any]) -> bool:
"""Save configuration to database, encrypting sensitive values."""
conn = None
try:
self._ensure_database_exists()
# Encrypt sensitive values before writing (original dict is untouched)
encrypted_data = self._encrypt_sensitive(config_data)
conn = sqlite3.connect(str(self.database_path))
# Use longer timeout (30s) to handle contention from enrichment workers
conn = sqlite3.connect(str(self.database_path), timeout=30.0)
conn.execute("PRAGMA journal_mode=WAL")
cursor = conn.cursor()
config_json = json.dumps(encrypted_data, indent=2)
@ -305,12 +313,14 @@ class ConfigManager:
""", (config_json,))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"Error: Could not save config to database: {e}")
return False
finally:
if conn:
conn.close()
def _load_from_config_file(self) -> Optional[Dict[str, Any]]:
"""Load configuration from config.json file (for migration)"""
@ -510,9 +520,15 @@ class ConfigManager:
self.config_data = config_data
def _save_config(self):
"""Save configuration to database"""
"""Save configuration to database with retry on lock."""
success = self._save_to_database(self.config_data)
if not success:
# Retry once after a brief wait (handles transient lock contention)
import time
time.sleep(1)
success = self._save_to_database(self.config_data)
if not success:
# Fallback: Try to save to config.json if database fails
print("[WARN] Database save failed - attempting file fallback")