fix(ip-pool): reconcile stale leases against live clones (§2.8, W1)
A job killed mid-flight never released its IP lease; with only 4 pool addresses a single leak exhausted the pool ("IP pool exhausted after 60s"). IpSlotPool.reconcile() releases leases whose owner clone-dir is gone; vm cleanup runs it only against the real build_vms base. Closes TODO §2.8.
This commit is contained in:
@@ -93,6 +93,32 @@ class IpSlotPool:
|
||||
leases[ip] = None
|
||||
self._write_leases(leases)
|
||||
|
||||
def reconcile(self, live_owners: set[str], *, dry_run: bool = False) -> list[str]:
|
||||
"""Release leases whose owner is not among *live_owners*.
|
||||
|
||||
Owners are clone names — identical to the clone directory names under
|
||||
``CloneBaseDir`` (``acquire`` is called with the clone name). A lease
|
||||
whose owner clone no longer exists on disk is a permanent leak: a job
|
||||
killed mid-flight (SIGKILL/crash) never calls :meth:`release`, and with
|
||||
only four pool addresses a single leak exhausts the pool (TODO §2.8).
|
||||
|
||||
Returns the IPs released — or, when *dry_run* is set, the IPs that
|
||||
*would* be released (no write performed).
|
||||
"""
|
||||
with _file_lock(self._lock_file):
|
||||
leases = self._read_leases()
|
||||
stale = [
|
||||
ip
|
||||
for ip, owner in leases.items()
|
||||
if owner is not None and owner not in live_owners
|
||||
]
|
||||
if not stale or dry_run:
|
||||
return stale
|
||||
for ip in stale:
|
||||
leases[ip] = None
|
||||
self._write_leases(leases)
|
||||
return stale
|
||||
|
||||
# ── internals ──────────────────────────────────────────────────────────
|
||||
|
||||
def _read_leases(self) -> dict[str, str | None]:
|
||||
|
||||
Reference in New Issue
Block a user