diff --git a/scripts/cpu-perf-check b/scripts/cpu-perf-check index 35622e92..2a15a6d0 100755 --- a/scripts/cpu-perf-check +++ b/scripts/cpu-perf-check @@ -112,21 +112,28 @@ def main( print(f'\n=== sustained {ncpu}-core load ({secs:.0f}s) — the real test ===') stop: float = time.perf_counter() + secs procs = [ - mp.Process(target=_burn, args=(stop,)) + mp.Process(target=_burn, args=(stop,), daemon=True) for _ in range(ncpu) ] for p in procs: p.start() - # skip the initial ~0.6s ramp, then sample steady-state + # skip the initial ~0.6s ramp, then sample steady-state. + # `try/finally` so a Ctrl-C / sampling error still reaps the + # burners instead of leaving stray CPU hogs (daemon=True is the + # backstop should we exit abnormally before the join). samples: list[int] = [] - time.sleep(0.6) - while time.perf_counter() < stop - 0.2: - if (fr := _cur_freqs_mhz()): - samples.append(sum(fr) // len(fr)) - time.sleep(0.3) - for p in procs: - p.join() + try: + time.sleep(0.6) + while time.perf_counter() < stop - 0.2: + if (fr := _cur_freqs_mhz()): + samples.append(sum(fr) // len(fr)) + time.sleep(0.3) + finally: + for p in procs: + p.terminate() + for p in procs: + p.join() if not (samples and pkg_max): print(' could not sample cur freq — assume OK') diff --git a/tests/conftest.py b/tests/conftest.py index 4acf9379..db2528f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -236,6 +236,12 @@ def _measure_sustained_headroom( # under-shoots and still trips the marginal cases). if frac >= throttle_gate: return 1. + # a 0/parked-core freq read would `ZeroDivisionError` the + # inverse below — silently swallowed by the outer `except` + # into a 1.0 (no-throttle), defeating the probe on exactly + # the broken box it should flag; read 0 as max throttle. + if frac <= 0: + return max_headroom return min(max_headroom, 1. / frac) except Exception: diff --git a/tractor/ipc/_uds.py b/tractor/ipc/_uds.py index 4c07a200..d1e9d2f6 100644 --- a/tractor/ipc/_uds.py +++ b/tractor/ipc/_uds.py @@ -205,16 +205,18 @@ class UDSAddress( else: prefix: str = 'no_runtime_actor' - # XXX, no live actor -> no `Aid` to key off, so append a - # per-CALL token: w/o a runtime the sockname is otherwise - # a pure fn of `(prefix, pid)`, so two `get_random()` - # calls in one proc alias to the SAME sockpath and the - # 2nd `.bind()` trips `EADDRINUSE`. The token makes each - # call a distinct file. Safe vs `._reap` cleanup which - # only reconstructs the runtime `{name}@{pid}` convention - # (above), never these `no_runtime_*` socks. - token: str = uuid4().hex[:6] - sockname: str = f'{prefix}@{pid}.{token}' + # XXX, no live actor -> no `Aid` to key off, so mix a + # per-CALL token into the NAME part for uniqueness: + # w/o a runtime the sockname is otherwise a pure fn of + # `(prefix, pid)`, so two `get_random()` calls in one + # proc alias to the SAME sockpath and the 2nd `.bind()` + # trips `EADDRINUSE`. Token goes BEFORE `@{pid}` so the + # canonical `...@{pid}.sock` suffix stays intact for the + # reapers keyed off it: `._testing._reap`'s + # `(?P.+)@(?P\d+)\.sock` regex, and the + # `spawn._reap` `{name}@{pid}.sock` reconstruction. + token: str = uuid4().hex[:8] + sockname: str = f'{prefix}.{token}@{pid}' sockpath: Path = Path(f'{sockname}.sock') return UDSAddress(