Spaces:
Running
Running
Hermes Agent commited on
Commit ·
4f6ca23
1
Parent(s): 2b1ef18
fix(persistence): eliminate container startup hang with tmp flock, fast backup, and corrupted DB auto-recovery
Browse files- entrypoint.sh +3 -1
- persistence_manager.py +5 -5
- vault_sync.py +16 -7
entrypoint.sh
CHANGED
|
@@ -182,9 +182,11 @@ preflight_persistence_gate() {
|
|
| 182 |
log_info "PREFLIGHT" "Existing storage.sqlite integrity verified: PRAGMA quick_check passed."
|
| 183 |
else
|
| 184 |
log_warn "PREFLIGHT" "storage.sqlite failed quick_check: $_CHK"
|
|
|
|
|
|
|
| 185 |
if [ -f "/vault_sync.py" ]; then
|
| 186 |
log_warn "PREFLIGHT" "Attempting cloud vault restore as fallback..."
|
| 187 |
-
python3 /vault_sync.py restore || true
|
| 188 |
fi
|
| 189 |
fi
|
| 190 |
fi
|
|
|
|
| 182 |
log_info "PREFLIGHT" "Existing storage.sqlite integrity verified: PRAGMA quick_check passed."
|
| 183 |
else
|
| 184 |
log_warn "PREFLIGHT" "storage.sqlite failed quick_check: $_CHK"
|
| 185 |
+
mv "$DATA_DIR/storage.sqlite" "$DATA_DIR/storage.sqlite.corrupt-$(date +%s)" 2>/dev/null || true
|
| 186 |
+
rm -f "$DATA_DIR/storage.sqlite-wal" "$DATA_DIR/storage.sqlite-shm" 2>/dev/null || true
|
| 187 |
if [ -f "/vault_sync.py" ]; then
|
| 188 |
log_warn "PREFLIGHT" "Attempting cloud vault restore as fallback..."
|
| 189 |
+
python3 /vault_sync.py restore --force || true
|
| 190 |
fi
|
| 191 |
fi
|
| 192 |
fi
|
persistence_manager.py
CHANGED
|
@@ -23,7 +23,7 @@ PERSISTENT_DB = DATA_DIR / "storage.sqlite"
|
|
| 23 |
BACKUP_DIR = DATA_DIR / "backups"
|
| 24 |
INCOMING_DIR = DATA_DIR / ".incoming"
|
| 25 |
LKG_DB = BACKUP_DIR / "last-known-good.sqlite"
|
| 26 |
-
LOCK_FILE =
|
| 27 |
ROLLING_LIMIT = max(1, int(os.getenv("OMNIROUTE_ROLLING_BACKUPS", "5")))
|
| 28 |
log = logging.getLogger("persistence")
|
| 29 |
|
|
@@ -70,7 +70,7 @@ def sqlite_status(path: Path, deep: bool = False) -> dict:
|
|
| 70 |
return result
|
| 71 |
connection = None
|
| 72 |
try:
|
| 73 |
-
connection = sqlite3.connect(f"file:{path}?mode=ro", uri=True, timeout=5)
|
| 74 |
quick = connection.execute("PRAGMA quick_check").fetchone()
|
| 75 |
result["quick_check"] = quick[0] if quick else "empty result"
|
| 76 |
if result["quick_check"] != "ok":
|
|
@@ -123,10 +123,10 @@ def sqlite_backup(source: Path, destination: Path) -> dict:
|
|
| 123 |
started = time.monotonic()
|
| 124 |
temporary = destination.with_name(f"{destination.name}.tmp-{os.getpid()}-{time.time_ns()}")
|
| 125 |
try:
|
| 126 |
-
source_connection = sqlite3.connect(f"file:{source}?mode=ro", uri=True, timeout=10)
|
| 127 |
destination_connection = sqlite3.connect(temporary, timeout=10)
|
| 128 |
try:
|
| 129 |
-
source_connection.backup(destination_connection, pages=
|
| 130 |
destination_connection.commit()
|
| 131 |
finally:
|
| 132 |
destination_connection.close()
|
|
@@ -182,7 +182,7 @@ def recover() -> dict:
|
|
| 182 |
candidates = [PERSISTENT_DB, LKG_DB]
|
| 183 |
candidates.extend(sorted(BACKUP_DIR.glob("storage-*.sqlite"), key=lambda p: p.stat().st_mtime, reverse=True))
|
| 184 |
for candidate in dict.fromkeys(candidates):
|
| 185 |
-
if sqlite_status(candidate, deep=
|
| 186 |
continue
|
| 187 |
if RUNTIME_DB.exists():
|
| 188 |
evidence = BACKUP_DIR / f"runtime-corrupt-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}.sqlite"
|
|
|
|
| 23 |
BACKUP_DIR = DATA_DIR / "backups"
|
| 24 |
INCOMING_DIR = DATA_DIR / ".incoming"
|
| 25 |
LKG_DB = BACKUP_DIR / "last-known-good.sqlite"
|
| 26 |
+
LOCK_FILE = Path(os.getenv("OMNIROUTE_LOCK_FILE", "/tmp/.persistence.lock"))
|
| 27 |
ROLLING_LIMIT = max(1, int(os.getenv("OMNIROUTE_ROLLING_BACKUPS", "5")))
|
| 28 |
log = logging.getLogger("persistence")
|
| 29 |
|
|
|
|
| 70 |
return result
|
| 71 |
connection = None
|
| 72 |
try:
|
| 73 |
+
connection = sqlite3.connect(f"file:{path}?mode=ro&nolock=1", uri=True, timeout=5)
|
| 74 |
quick = connection.execute("PRAGMA quick_check").fetchone()
|
| 75 |
result["quick_check"] = quick[0] if quick else "empty result"
|
| 76 |
if result["quick_check"] != "ok":
|
|
|
|
| 123 |
started = time.monotonic()
|
| 124 |
temporary = destination.with_name(f"{destination.name}.tmp-{os.getpid()}-{time.time_ns()}")
|
| 125 |
try:
|
| 126 |
+
source_connection = sqlite3.connect(f"file:{source}?mode=ro&nolock=1", uri=True, timeout=10)
|
| 127 |
destination_connection = sqlite3.connect(temporary, timeout=10)
|
| 128 |
try:
|
| 129 |
+
source_connection.backup(destination_connection, pages=-1)
|
| 130 |
destination_connection.commit()
|
| 131 |
finally:
|
| 132 |
destination_connection.close()
|
|
|
|
| 182 |
candidates = [PERSISTENT_DB, LKG_DB]
|
| 183 |
candidates.extend(sorted(BACKUP_DIR.glob("storage-*.sqlite"), key=lambda p: p.stat().st_mtime, reverse=True))
|
| 184 |
for candidate in dict.fromkeys(candidates):
|
| 185 |
+
if sqlite_status(candidate, deep=False)["status"] != "HEALTHY":
|
| 186 |
continue
|
| 187 |
if RUNTIME_DB.exists():
|
| 188 |
evidence = BACKUP_DIR / f"runtime-corrupt-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}.sqlite"
|
vault_sync.py
CHANGED
|
@@ -39,15 +39,24 @@ def get_hf_api():
|
|
| 39 |
logger.error(f"huggingface_hub import error: {exc}")
|
| 40 |
return None, f"huggingface_hub import error: {exc}"
|
| 41 |
|
| 42 |
-
def restore_from_vault() -> tuple[bool, str]:
|
| 43 |
"""Restores storage.sqlite from private HF Dataset if local database is absent or empty."""
|
| 44 |
os.makedirs(DATA_DIR, exist_ok=True)
|
| 45 |
|
| 46 |
-
# If a valid local database already exists with positive size, keep local state
|
| 47 |
-
if os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0:
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_API_KEY")
|
| 53 |
if not token:
|
|
@@ -143,7 +152,7 @@ def backup_to_vault() -> tuple[bool, str]:
|
|
| 143 |
if __name__ == "__main__":
|
| 144 |
action = sys.argv[1] if len(sys.argv) > 1 else "backup"
|
| 145 |
if action == "restore":
|
| 146 |
-
restore_from_vault()
|
| 147 |
elif action == "backup":
|
| 148 |
backup_to_vault()
|
| 149 |
else:
|
|
|
|
| 39 |
logger.error(f"huggingface_hub import error: {exc}")
|
| 40 |
return None, f"huggingface_hub import error: {exc}"
|
| 41 |
|
| 42 |
+
def restore_from_vault(force: bool = False) -> tuple[bool, str]:
|
| 43 |
"""Restores storage.sqlite from private HF Dataset if local database is absent or empty."""
|
| 44 |
os.makedirs(DATA_DIR, exist_ok=True)
|
| 45 |
|
| 46 |
+
# If a valid local database already exists with positive size AND passes quick_check, keep local state
|
| 47 |
+
if not force and os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0:
|
| 48 |
+
try:
|
| 49 |
+
chk_conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro&nolock=1", uri=True, timeout=3.0)
|
| 50 |
+
res = chk_conn.execute("PRAGMA quick_check;").fetchone()
|
| 51 |
+
chk_conn.close()
|
| 52 |
+
if res and res[0] == "ok":
|
| 53 |
+
msg = f"Authoritative local database found at {DB_PATH} ({os.path.getsize(DB_PATH)} bytes) and verified healthy."
|
| 54 |
+
logger.info(msg)
|
| 55 |
+
return True, msg
|
| 56 |
+
else:
|
| 57 |
+
logger.warning(f"Local database exists but failed quick_check: {res}. Forcing restore from cloud vault...")
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.warning(f"Local database check threw exception: {e}. Forcing restore from cloud vault...")
|
| 60 |
|
| 61 |
token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_API_KEY")
|
| 62 |
if not token:
|
|
|
|
| 152 |
if __name__ == "__main__":
|
| 153 |
action = sys.argv[1] if len(sys.argv) > 1 else "backup"
|
| 154 |
if action == "restore":
|
| 155 |
+
restore_from_vault(force=("--force" in sys.argv))
|
| 156 |
elif action == "backup":
|
| 157 |
backup_to_vault()
|
| 158 |
else:
|