Skip to content

Commit 73b8724

Browse files
Copilotaofei
andauthored
fix: close cached content to prevent memory leak in Goproxy.serveFetchDownload (#152)
* Initial plan * Fix memory leak by adding defer content.Close() in serveFetchDownload Co-authored-by: aofei <[email protected]> * Simplify test code based on code review feedback Co-authored-by: aofei <[email protected]> * Add .gitignore to exclude build artifacts Co-authored-by: aofei <[email protected]> * Move test to subtest and remove .gitignore per code review Co-authored-by: aofei <[email protected]> * Use existing closerFunc instead of testReadCloser Co-authored-by: aofei <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: aofei <[email protected]>
1 parent 1311b8d commit 73b8724

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

goproxy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ func (g *Goproxy) serveFetchDownload(rw http.ResponseWriter, req *http.Request,
260260
}
261261

262262
if content, err := g.cache(req.Context(), target); err == nil {
263+
defer content.Close()
263264
responseSuccess(rw, req, content, contentType, cacheControlMaxAge)
264265
return
265266
} else if !errors.Is(err, fs.ErrNotExist) {

goproxy_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,52 @@ func TestGoproxyServeFetchDownload(t *testing.T) {
872872
}
873873
})
874874
}
875+
876+
t.Run("CachedContentClosed", func(t *testing.T) {
877+
// Track whether Close was called on the cached content
878+
var closeCalled bool
879+
880+
// Create a custom cacher that returns closable content
881+
cacher := &testCacher{
882+
Cacher: DirCacher(t.TempDir()),
883+
get: func(ctx context.Context, c Cacher, name string) (io.ReadCloser, error) {
884+
return struct {
885+
io.Reader
886+
io.Closer
887+
}{
888+
Reader: strings.NewReader(info),
889+
Closer: closerFunc(func() error {
890+
closeCalled = true
891+
return nil
892+
}),
893+
}, nil
894+
},
895+
}
896+
897+
g := &Goproxy{
898+
Fetcher: &GoFetcher{
899+
Env: []string{"GOPROXY=off", "GOSUMDB=off"},
900+
TempDir: t.TempDir(),
901+
},
902+
Cacher: cacher,
903+
TempDir: t.TempDir(),
904+
Logger: slog.New(slog.DiscardHandler),
905+
}
906+
g.initOnce.Do(g.init)
907+
908+
rec := httptest.NewRecorder()
909+
target := "example.com/@v/v1.0.0.info"
910+
g.serveFetchDownload(rec, httptest.NewRequest("", "/", nil), target, "example.com", "v1.0.0", false)
911+
912+
if !closeCalled {
913+
t.Error("cached content was not closed, potential memory leak")
914+
}
915+
916+
recr := rec.Result()
917+
if got, want := recr.StatusCode, http.StatusOK; got != want {
918+
t.Errorf("got status %d, want %d", got, want)
919+
}
920+
})
875921
}
876922

877923
func TestGoproxyServeSumDB(t *testing.T) {

0 commit comments

Comments
 (0)