Skip to content

fix(backend): close the ReadCloser returned by PullBlob in push#509

Open
SAY-5 wants to merge 1 commit intomodelpack:mainfrom
SAY-5:fix/push-close-pull-blob-reader
Open

fix(backend): close the ReadCloser returned by PullBlob in push#509
SAY-5 wants to merge 1 commit intomodelpack:mainfrom
SAY-5:fix/push-close-pull-blob-reader

Conversation

@SAY-5
Copy link
Copy Markdown

@SAY-5 SAY-5 commented Apr 17, 2026

Problem

pushIfNotExist pulls each blob's content from the source storage and
then hands it to Blobs().Push wrapped in io.NopCloser:

content, err := src.PullBlob(ctx, repo, desc.Digest.String())
if err != nil {
    return err
}

reader := pb.Add(prompt, desc.Digest.String(), desc.Size, content)
if err := dst.Blobs().Push(ctx, desc, io.NopCloser(reader)); err != nil {
    ...
}

The io.NopCloser is intentional: Close on the distribution reader
returns an error (see #50), so we don't let Push propagate that error.
But the wrapper also prevents the original content from ever being
closed. On both success and error paths, the underlying file descriptor
(or HTTP body) leaks for every blob we push — and Push is called once
per object in the image. #491.

Fix

Add defer content.Close() immediately after the nil-error check. The
existing NopCloser still prevents Push from calling Close on its
own, so the workaround for #50 is preserved. defer handles both the
success path and every early-return error path.

  content, err := src.PullBlob(ctx, repo, desc.Digest.String())
  if err != nil {
      return err
  }
+ defer content.Close()

  reader := pb.Add(prompt, desc.Digest.String(), desc.Size, content)

Fixes #491

pushIfNotExist pulled each blob's content from the source storage and then
handed it to Blobs().Push wrapped in io.NopCloser. The NopCloser is there
on purpose (Close on the distribution reader returns an error, see modelpack#50),
but it also means the original ReadCloser from PullBlob was never closed
on any path, leaking a file descriptor or HTTP body per blob.

Add a `defer content.Close()` immediately after the nil-error check so the
original reader is released on both success and error paths. The existing
NopCloser wrapper still prevents Push from calling Close itself, so the
workaround for modelpack#50 is preserved.

Fixes modelpack#491

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a resource leak in pkg/backend/push.go by ensuring that blob content is properly closed using a defer statement. Feedback was provided to simplify the accompanying comment for better readability and to move historical context to the commit message.

Comment thread pkg/backend/push.go
Comment on lines +179 to +185
// The ReadCloser returned by PullBlob was previously never closed on
// either path, leaking the underlying file descriptor (or HTTP body)
// for every blob we pushed. Close on the distribution implementation
// returns an error (#50), which is why we still wrap the reader with
// io.NopCloser below, but we DO need to close `content` ourselves.
// See #491.
defer content.Close()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The added comment is quite verbose and contains historical context that is better suited for a commit message. Additionally, it partially duplicates the explanation for the io.NopCloser workaround located just below. Consolidating this into a concise note about resource management would improve readability.

Suggested change
// The ReadCloser returned by PullBlob was previously never closed on
// either path, leaking the underlying file descriptor (or HTTP body)
// for every blob we pushed. Close on the distribution implementation
// returns an error (#50), which is why we still wrap the reader with
// io.NopCloser below, but we DO need to close `content` ourselves.
// See #491.
defer content.Close()
// Ensure the blob content is closed to avoid leaking resources (#491).
// We use defer here and io.NopCloser below to work around the distribution
// library's Close() implementation which returns a known error (#50).
defer content.Close()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Push leaks ReadCloser from PullBlob on both success and error paths

1 participant