"""Ends a spawned Chrome whose kernel died without closing it. `spawn()` starts one of these beside each owned Chrome, reading a pipe the kernel holds the only write end of. A kernel that closes its browser writes a byte first, and this exits leaving everything alone. End of input without that byte means the kernel died, SIGKILL included, where its atexit cleanup never ran: Chrome's process group is ended, and its lock and temporary profile are removed, so no headless Chrome outlives the kernel that started it. """ from __future__ import annotations import asyncio import os from pathlib import Path import select import shutil import sys sys.path.insert(0, str(Path(__file__).resolve().parents[2])) import albedo_proc # noqa: E402 # How often a quiet pipe checks whether Chrome ended on its own. POLL_SECONDS = 5 def main(pgid: int, leader: str, profile: Path, lock: Path, temporary: bool) -> None: group = albedo_proc.Group(pgid, leader or None) while True: ready, _, _ = select.select([0], [], [], POLL_SECONDS) if ready: if os.read(0, 1): return # the kernel closed the browser itself break if not albedo_proc.alive(group): return # Chrome ended; the kernel releases what it owned asyncio.run(albedo_proc.terminate([group], term=2.0)) if temporary: shutil.rmtree(profile, ignore_errors=True) else: lock.unlink(missing_ok=True) if __name__ == "__main__": main( int(sys.argv[1]), sys.argv[2], Path(sys.argv[3]), Path(sys.argv[4]), sys.argv[5] == "temporary", )