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
Files changed (3) hide show
  1. entrypoint.sh +3 -1
  2. persistence_manager.py +5 -5
  3. 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 = DATA_DIR / ".persistence.lock"
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=256, sleep=0.05)
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=True)["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"
 
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 as authoritative
47
- if os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0:
48
- msg = f"Authoritative local database found at {DB_PATH} ({os.path.getsize(DB_PATH)} bytes)."
49
- logger.info(msg)
50
- return True, msg
 
 
 
 
 
 
 
 
 
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: