Pull progress: split the stream on CR too
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 <noreply@anthropic.com>
This commit is contained in:
parent
e41cd48272
commit
3ae1578325
@ -114,7 +114,7 @@ def _pull_one(image: str, verbose: bool = True) -> None:
|
|||||||
# per-layer lines; forward the informative ones (throttled).
|
# per-layer lines; forward the informative ones (throttled).
|
||||||
proc = subprocess.Popen(['docker', 'pull', ref],
|
proc = subprocess.Popen(['docker', 'pull', ref],
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
text=True, bufsize=1)
|
bufsize=0)
|
||||||
last_line = ''
|
last_line = ''
|
||||||
last_show = 0.0
|
last_show = 0.0
|
||||||
layer_prog = {} # layer id -> (done_bytes, total_bytes): the raw
|
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[:-len(suf)]) * m)
|
||||||
return int(float(s or 0))
|
return int(float(s or 0))
|
||||||
|
|
||||||
for line in proc.stdout:
|
# docker progress lines end with \r (carriage-return refresh), not
|
||||||
last_line = line.strip()
|
# \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)
|
m = _re.match(r'^([0-9a-f]{12}): Downloading.*?([\d.]+[kKMG]?B)/([\d.]+[kKMG]?B)', line)
|
||||||
if m:
|
if m:
|
||||||
layer_prog[m.group(1)] = (_bytes(m.group(2)), _bytes(m.group(3)))
|
layer_prog[m.group(1)] = (_bytes(m.group(2)), _bytes(m.group(3)))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user