diff --git a/agents/fethry/work/queue-328/BUILD.md b/agents/fethry/work/queue-328/BUILD.md index 6263b4558..dbc02d980 100644 --- a/agents/fethry/work/queue-328/BUILD.md +++ b/agents/fethry/work/queue-328/BUILD.md @@ -92,3 +92,16 @@ Finally restored exact test bytes/SHA, empty git diff --stat HEAD, empty porcela no own node/Chromium remaining. Scan saw only inspection Python, which exited. Slot returned fethry0039 while Sage rules; no test/runner modifications. Raw and normalized receipts retained in evidence/aborted-sage4194 with provenance and explicit EXCLUDED.md. + +## sage4196 priority ruling — preparation only + +Sage reports active ananicy-cpp (15s checks/apply_nice); no specific Python rule proven. +Priority drift alone is no longer a stop condition and has no bearing on test validity. +Fresh CLEAR required after Della. Begin again at attempt1, initial absolute nice19, Python-B. + +Only stress.py instrumentation added: a read-only100ms /proc sampler records runner and +timeout/Node descendant pid, comm, nice and elapsed sample time for every attempt, retaining +changes. Existing wait, timeout, verdict, phase order, mutation and restoration paths unchanged. +No renice loop or host/rule change. Test/manifest remain identical to36668b698. Static AST +parse only; no runner or tests executed. Stop remains fixed red, dirty tree or other unexpected +event. Final clean checks include full git status --porcelain as well as diff --stat. diff --git a/agents/fethry/work/queue-328/stress.py b/agents/fethry/work/queue-328/stress.py index f7a1012f8..e6721f6c4 100644 --- a/agents/fethry/work/queue-328/stress.py +++ b/agents/fethry/work/queue-328/stress.py @@ -8,6 +8,7 @@ from pathlib import Path import re import signal import subprocess +import threading import time ROOT = Path(__file__).resolve().parents[4] @@ -21,6 +22,37 @@ def digest(value): return hashlib.sha256(value).hexdigest() +def sample_nice(child_pid, records, stopped): + """Read-only samples of the runner and timeout/Node descendants (sage-4196).""" + seen = set() + started = time.monotonic() + while True: + pending = [os.getpid(), child_pid] + visited = set() + while pending: + pid = pending.pop() + if pid in visited: + continue + visited.add(pid) + proc = Path('/proc') / str(pid) + try: + stat = (proc / 'stat').read_text() + fields = stat.rsplit(')', 1)[1].split() + nice = int(fields[16]) # stat field19, after pid and parenthesized comm + command = stat.split('(', 1)[1].rsplit(')', 1)[0] + key = (pid, command, nice) + if key not in seen: + records.append({'pid': pid, 'command': command, 'nice': nice, + 'seconds': round(time.monotonic() - started, 3)}) + seen.add(key) + if pid != os.getpid(): + pending.extend(map(int, (proc / 'task' / str(pid) / 'children').read_text().split())) + except (OSError, ValueError): + pass # Short-lived children may exit between the read-only samples. + if stopped.wait(.1): + return + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--clear-key', required=True, help='Sage message authorizing this test slot') @@ -55,6 +87,9 @@ def main(): signals = [] with log.open('w') as stream: child = subprocess.Popen(command, cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT, start_new_session=True) + nice_samples, nice_stop = [], threading.Event() + sampler = threading.Thread(target=sample_nice, args=(child.pid, nice_samples, nice_stop), daemon=True) + sampler.start() try: code = child.wait(timeout=135) except subprocess.TimeoutExpired: @@ -62,6 +97,8 @@ def main(): signals.append('KILL') code = child.wait() finally: + nice_stop.set() + sampler.join() try: os.killpg(child.pid, signal.SIGTERM) signals.append('TERM') @@ -81,7 +118,7 @@ def main(): and "+ code: 'library-sync-busy'" in text and "- code: 'library-fetch-no-grant'" in text) entry = {'phase': phase, 'attempt': number, 'command': command, 'TMPDIR': temporary, 'load': os.getloadavg(), 'exit': code, 'counts': counts, 'green': green, 'race': race, 'seconds': round(time.monotonic() - before, 3), - 'cleanupSignals': signals, 'TAP': log.name, 'TAPSHA256': digest(log.read_bytes())} + 'niceSamples': nice_samples, 'cleanupSignals': signals, 'TAP': log.name, 'TAPSHA256': digest(log.read_bytes())} report['runs'].append(entry) save() print(json.dumps(entry), flush=True)