diff --git a/evalharness/sandbox/prefetch.py b/evalharness/sandbox/prefetch.py index 991ad32..e59d24e 100644 --- a/evalharness/sandbox/prefetch.py +++ b/evalharness/sandbox/prefetch.py @@ -114,7 +114,7 @@ def _pull_one(image: str, verbose: bool = True) -> None: # per-layer lines; forward the informative ones (throttled). proc = subprocess.Popen(['docker', 'pull', ref], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, bufsize=1) + bufsize=0) last_line = '' last_show = 0.0 layer_prog = {} # layer id -> (done_bytes, total_bytes): the raw @@ -130,8 +130,33 @@ def _pull_one(image: str, verbose: bool = True) -> None: return int(float(s[:-len(suf)]) * m) return int(float(s or 0)) - for line in proc.stdout: - last_line = line.strip() + # docker progress lines end with \r (carriage-return refresh), not + # \n -- readline() buffered them until a rare \n arrived, so the + # Downloading snapshots (and the ⏳ summary fed by them) never + # surfaced. Split on BOTH terminators, chunked reads. + import io as _io + stream = _io.TextIOWrapper(proc.stdout, errors='replace') + buf = '' + _lines_iter = iter(lambda: None, 1) # placeholder, replaced below + + def _iter_lines(): + nonlocal buf + while True: + chunk = stream.read(512) + if not chunk: + if buf: + yield buf + return + buf += chunk + parts = buf.split('\r') if '\r' in buf else buf.split('\n') + if len(parts) > 1: + for p in parts[:-1]: + yield p + buf = parts[-1] + + for line in _iter_lines(): + line = line.strip('\n ') + last_line = line m = _re.match(r'^([0-9a-f]{12}): Downloading.*?([\d.]+[kKMG]?B)/([\d.]+[kKMG]?B)', line) if m: layer_prog[m.group(1)] = (_bytes(m.group(2)), _bytes(m.group(3)))