Break ALL locks in cache directory (manual recovery). Uses the SQLite database to clear locks, plus scans for legacy .lock files. Args: cache_dir: Global cache directory Returns: Number of locks broken
(cache_dir: Path)
| 77 | |
| 78 | |
| 79 | def force_unlock_cache(cache_dir: Path) -> int: |
| 80 | """ |
| 81 | Break ALL locks in cache directory (manual recovery). |
| 82 | |
| 83 | Uses the SQLite database to clear locks, plus scans for legacy .lock files. |
| 84 | |
| 85 | Args: |
| 86 | cache_dir: Global cache directory |
| 87 | |
| 88 | Returns: |
| 89 | Number of locks broken |
| 90 | """ |
| 91 | if not cache_dir.exists(): |
| 92 | return 0 |
| 93 | |
| 94 | broken_count = 0 |
| 95 | |
| 96 | # Clear locks from the database |
| 97 | db = get_lock_database() |
| 98 | all_locks = db.list_all_locks() |
| 99 | for lock in all_locks: |
| 100 | lock_name = lock["lock_name"] |
| 101 | # Only clear locks that relate to paths under this cache_dir |
| 102 | if str(cache_dir) in lock_name: |
| 103 | db.force_break(lock_name) |
| 104 | broken_count += 1 |
| 105 | logger.info(f"Force-broke lock: {lock_name}") |
| 106 | |
| 107 | # Also clean up any legacy .lock files on disk |
| 108 | for lock_file in cache_dir.rglob("*.lock"): |
| 109 | try: |
| 110 | if lock_file.exists(): |
| 111 | lock_file.unlink() |
| 112 | logger.info(f"Removed legacy lock file: {lock_file}") |
| 113 | broken_count += 1 |
| 114 | |
| 115 | # Remove associated .pid file |
| 116 | pid_file = lock_file.with_suffix(lock_file.suffix + ".pid") |
| 117 | if pid_file.exists(): |
| 118 | pid_file.unlink() |
| 119 | |
| 120 | except OSError as e: |
| 121 | logger.warning(f"Failed to remove lock file {lock_file}: {e}") |
| 122 | |
| 123 | return broken_count |
| 124 | |
| 125 | |
| 126 | def cleanup_stale_locks(cache_dir: Path) -> int: |