Find and remove only stale locks (PID-based detection). Safe to run anytime - only removes locks from dead processes. Args: cache_dir: Global cache directory Returns: Number of stale locks cleaned
(cache_dir: Path)
| 124 | |
| 125 | |
| 126 | def cleanup_stale_locks(cache_dir: Path) -> int: |
| 127 | """ |
| 128 | Find and remove only stale locks (PID-based detection). |
| 129 | |
| 130 | Safe to run anytime - only removes locks from dead processes. |
| 131 | |
| 132 | Args: |
| 133 | cache_dir: Global cache directory |
| 134 | |
| 135 | Returns: |
| 136 | Number of stale locks cleaned |
| 137 | """ |
| 138 | if not cache_dir.exists(): |
| 139 | return 0 |
| 140 | |
| 141 | # Clean stale locks from the database |
| 142 | db = get_lock_database() |
| 143 | cleaned_count = db.cleanup_stale_locks() |
| 144 | |
| 145 | # Also clean up any legacy .lock files on disk |
| 146 | from ci.util.file_lock_rw import break_stale_lock as _break_stale_lock_legacy |
| 147 | |
| 148 | for lock_file in cache_dir.rglob("*.lock"): |
| 149 | try: |
| 150 | # Check for legacy .lock.pid metadata files |
| 151 | pid_file = lock_file.with_suffix(lock_file.suffix + ".pid") |
| 152 | if pid_file.exists(): |
| 153 | if _break_stale_lock_legacy(lock_file): |
| 154 | cleaned_count += 1 |
| 155 | except KeyboardInterrupt as ki: |
| 156 | handle_keyboard_interrupt(ki) |
| 157 | raise |
| 158 | except Exception as e: |
| 159 | logger.warning(f"Error checking lock {lock_file}: {e}") |
| 160 | |
| 161 | return cleaned_count |
| 162 | |
| 163 | |
| 164 | def list_active_locks(cache_dir: Path) -> list[LockInfo]: |