Kill zccache processes whose CWD is inside build_dir. When ninja is interrupted, zccache.exe wrappers can survive because they communicate through a daemon and are not direct children of ninja. Their CWD remains set to the build directory, which on Windows holds a kernel handle that
(build_dir: Path)
| 204 | |
| 205 | |
| 206 | def _kill_zombie_zccache(build_dir: Path) -> None: |
| 207 | """Kill zccache processes whose CWD is inside build_dir. |
| 208 | |
| 209 | When ninja is interrupted, zccache.exe wrappers can survive because they |
| 210 | communicate through a daemon and are not direct children of ninja. Their |
| 211 | CWD remains set to the build directory, which on Windows holds a kernel |
| 212 | handle that prevents directory deletion. |
| 213 | """ |
| 214 | if sys.platform != "win32": |
| 215 | return |
| 216 | try: |
| 217 | import psutil # noqa: PLC0415 - lazy import, only needed on Windows cleanup |
| 218 | except ImportError: |
| 219 | return |
| 220 | build_str = str(build_dir.resolve()) |
| 221 | killed = 0 |
| 222 | for proc in psutil.process_iter(["pid", "name"]): |
| 223 | try: |
| 224 | name = proc.info["name"] or "" |
| 225 | if "zccache" not in name.lower() or "daemon" in name.lower(): |
| 226 | continue |
| 227 | cwd = proc.cwd() |
| 228 | if cwd and (cwd == build_str or cwd.startswith(build_str + os.sep)): |
| 229 | proc.kill() |
| 230 | killed += 1 |
| 231 | except (psutil.NoSuchProcess, psutil.AccessDenied, OSError): |
| 232 | continue |
| 233 | if killed: |
| 234 | _ts_print( |
| 235 | f"[MESON] 🧹 Killed {killed} zombie zccache process(es) holding build directory" |
| 236 | ) |
| 237 | time.sleep(0.5) # Brief wait for Windows to release handles |
| 238 | |
| 239 | |
| 240 | def _safe_rmtree(build_dir: Path) -> None: |
no test coverage detected