From 3ae1578325eebf8d237539a74f61dbe5bd433754 Mon Sep 17 00:00:00 2001 From: sora <2075279110@qq.com> Date: Thu, 17 Sep 2026 03:51:02 +0000 Subject: [PATCH] Pull progress: split the stream on CR too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker refreshes Downloading lines with \r, not \n -- readline() parked them in the buffer, so the byte snapshots never surfaced and the overall-progress dict stayed empty (the ⏳ summary never printed). Chunked reads now split on both terminators; verified with a synthetic \r-stream that the parser yields the refresh lines and computes the 52.2/65.1MB-style summary. Co-Authored-By: Claude --- evalharness/sandbox/prefetch.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) 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)))