fix(http): close prefetch response when temporary-file setup fails

This commit is contained in:
Levi committed 2026-09-16 23:43:43 +08:00
1 parent 5342be16d9
commit e27c7dddb4
2 files changed
+85 -10

No files matched your search

+11 -10
View File
@@ -512,6 +512,17 @@ func (f *Fetcher) asyncPrefetch() {
return
}
defer func() {
// Close the response even if temporary-file setup fails or shutdown
// rejects a late prefetch. Otherwise the server can remain blocked writing.
f.resolveRespLock.Lock()
if f.resolveResp != nil {
f.resolveResp.Body.Close()
f.resolveResp = nil
}
f.resolveRespLock.Unlock()
}()
// Create temporary file for prefetch data
tempDir := ""
if f.ctl != nil && f.ctl.TempDir != "" {
@@ -537,16 +548,6 @@ func (f *Fetcher) asyncPrefetch() {
f.prefetchFile = tmpFile
f.prefetchFilePath = tmpFile.Name()
defer func() {
// Close response body when prefetch stops
f.resolveRespLock.Lock()
if f.resolveResp != nil {
f.resolveResp.Body.Close()
f.resolveResp = nil
}
f.resolveRespLock.Unlock()
}()
buf := make([]byte, 32*1024) // 32KB buffer
reader := f.responseReader(resp)
@@ -0,0 +1,74 @@
package http
import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"github.com/GopeedLab/gopeed/internal/controller"
"github.com/GopeedLab/gopeed/internal/tempfiles"
)
type prefetchSetupBody struct {
io.Reader
closed bool
}
func (b *prefetchSetupBody) Close() error {
b.closed = true
return nil
}
func TestPrefetchSetupFailureClosesResponse(t *testing.T) {
for _, closedScope := range []bool{false, true} {
name := "invalid_temp_directory"
if closedScope {
name = "downloader_already_closed"
}
t.Run(name, func(t *testing.T) {
root := t.TempDir()
scope := &tempfiles.Scope{}
dir := root
if closedScope {
if err := scope.Close(); err != nil {
t.Fatal(err)
}
} else {
dir = filepath.Join(root, "file")
if err := os.WriteFile(dir, nil, 0600); err != nil {
t.Fatal(err)
}
}
body := &prefetchSetupBody{Reader: strings.NewReader("unused response")}
f := &Fetcher{
ctl: &controller.Controller{TempDir: dir, TempFiles: scope},
resolveResp: &http.Response{Body: body},
}
f.asyncPrefetch()
if f.prefetchErr == nil {
t.Fatal("expected temporary-file initialization to fail")
}
if !body.closed {
t.Error("prefetch setup failure left the response body open")
}
if f.resolveResp != nil {
t.Error("prefetch retained its failed resolve response")
}
if !f.prefetchDone.Load() {
t.Error("prefetch did not report completion")
}
if closedScope {
entries, err := os.ReadDir(root)
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Errorf("late prefetch left temporary files: %v", entries)
}
}
})
}
}