Thread-safe tracker for build process trees. This class maintains a registry of active build processes and provides methods to detect and cleanup orphaned process trees.
| 68 | |
| 69 | |
| 70 | class BuildProcessTracker: |
| 71 | """Thread-safe tracker for build process trees. |
| 72 | |
| 73 | This class maintains a registry of active build processes and provides |
| 74 | methods to detect and cleanup orphaned process trees. |
| 75 | """ |
| 76 | |
| 77 | def __init__(self, registry_file: Path): |
| 78 | """Initialize the tracker. |
| 79 | |
| 80 | Args: |
| 81 | registry_file: Path to JSON file for persisting process trees |
| 82 | """ |
| 83 | self.registry_file = registry_file |
| 84 | self.lock = threading.Lock() |
| 85 | self._registry: dict[int, ProcessTreeInfo] = {} |
| 86 | self._load_registry() |
| 87 | |
| 88 | def _load_registry(self) -> None: |
| 89 | """Load registry from disk (if it exists).""" |
| 90 | if not self.registry_file.exists(): |
| 91 | return |
| 92 | |
| 93 | try: |
| 94 | with open(self.registry_file, "r") as f: |
| 95 | data = json.load(f) |
| 96 | |
| 97 | with self.lock: |
| 98 | self._registry = { |
| 99 | int(client_pid): ProcessTreeInfo.from_dict(info) |
| 100 | for client_pid, info in data.items() |
| 101 | } |
| 102 | |
| 103 | logging.info(f"Loaded {len(self._registry)} process trees from registry") |
| 104 | except KeyboardInterrupt as ki: |
| 105 | handle_keyboard_interrupt(ki) |
| 106 | raise |
| 107 | except Exception as e: |
| 108 | logging.warning(f"Failed to load process registry: {e}") |
| 109 | self._registry = {} |
| 110 | |
| 111 | def _save_registry(self) -> None: |
| 112 | """Save registry to disk atomically.""" |
| 113 | try: |
| 114 | # Prepare data for serialization |
| 115 | data = { |
| 116 | str(client_pid): info.to_dict() |
| 117 | for client_pid, info in self._registry.items() |
| 118 | } |
| 119 | |
| 120 | # Atomic write |
| 121 | temp_file = self.registry_file.with_suffix(".tmp") |
| 122 | with open(temp_file, "w") as f: |
| 123 | json.dump(data, f, indent=2) |
| 124 | |
| 125 | temp_file.replace(self.registry_file) |
| 126 | |
| 127 | except KeyboardInterrupt as ki: |