From 0e93e961ed2639cba6ed6a42289f29e22d071f9c Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Fri, 25 Sep 2026 04:33:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(imagepush):=20=E6=8E=A8=E9=80=81=E4=B8=8E?= =?UTF-8?q?=E6=8B=89=E5=8F=96=E5=85=B1=E7=94=A8=E4=B8=80=E4=B8=AA=20HTTP?= =?UTF-8?q?=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=A4=8D=E7=94=A8=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E5=87=BA=E9=94=99=E6=97=B6=E5=85=88=E8=AF=BB?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E4=BD=93=E5=86=8D=E5=85=B3=E9=97=AD=EF=BC=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E4=B8=8D=E5=86=8D=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/imagepush/mirror.go | 6 +++++- internal/imagepush/push.go | 23 ++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/internal/imagepush/mirror.go b/internal/imagepush/mirror.go index 2df2078..253a1d1 100644 --- a/internal/imagepush/mirror.go +++ b/internal/imagepush/mirror.go @@ -109,11 +109,15 @@ type Source struct { tokens map[string]string // host/repo → bearer token } +// sourceClient is shared by every Source without its own Client, so reads +// reuse keep-alive connections instead of opening one per request. +var sourceClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyFromEnvironment, ResponseHeaderTimeout: sourceRequestTimeout}} + func (s *Source) client() *http.Client { if s.Client != nil { return s.Client } - return &http.Client{Transport: &http.Transport{Proxy: http.ProxyFromEnvironment, ResponseHeaderTimeout: sourceRequestTimeout}} + return sourceClient } func (s *Source) url(r SourceRef, tail string) string { diff --git a/internal/imagepush/push.go b/internal/imagepush/push.go index 87cee52..f5e8916 100644 --- a/internal/imagepush/push.go +++ b/internal/imagepush/push.go @@ -240,10 +240,11 @@ func (p *Pusher) uploadBlob(ctx context.Context, r Ref, digest string, size int6 if err != nil { return err } - start.Body.Close() if start.StatusCode != http.StatusAccepted { + defer start.Body.Close() return statusError("start upload", start) } + drainClose(start) loc, err := start.Location() if err != nil { return fmt.Errorf("start upload: %w", err) @@ -261,10 +262,11 @@ func (p *Pusher) uploadBlob(ctx context.Context, r Ref, digest string, size int6 if err != nil { return err } - put.Body.Close() if put.StatusCode != http.StatusCreated { + defer put.Body.Close() return statusError("upload", put) } + drainClose(put) p.logf("pushed %s (%d bytes)", digest, size) return nil } @@ -311,13 +313,17 @@ func (p *Pusher) do(ctx context.Context, method, target string, body io.Reader, } c := p.Client if c == nil { - // No overall timeout: a modpack layer can take minutes on a slow disk, and - // the Job's activeDeadlineSeconds is the real bound. - c = &http.Client{Transport: &http.Transport{ResponseHeaderTimeout: 2 * time.Minute}} + c = pushClient } return c.Do(req) } +// pushClient is shared by every Pusher without its own Client, so a restore of +// many images reuses keep-alive connections instead of opening one per +// request. No overall timeout: a modpack layer can take minutes on a slow disk, +// and the caller's deadline is the real bound. +var pushClient = &http.Client{Transport: &http.Transport{ResponseHeaderTimeout: 2 * time.Minute}} + // retry runs fn up to Attempts times, backing off between tries. A refusal the // registry will repeat (401/403/4xx other than 408/429) is returned at once. A 503 // with Retry-After is waited out without using up an attempt, for as long as @@ -394,6 +400,13 @@ func (e *StatusError) Error() string { return fmt.Sprintf("%s: registry answered %d: %s", e.Op, e.Code, e.Body) } +// drainClose reads what is left of a small response body so its connection +// goes back to the pool. +func drainClose(resp *http.Response) { + io.Copy(io.Discard, io.LimitReader(resp.Body, 64<<10)) + resp.Body.Close() +} + func statusError(op string, resp *http.Response) error { b, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) se := &StatusError{Op: op, Code: resp.StatusCode, Body: strings.TrimSpace(string(b))}