From fa622a4063ad72939d3f031eac95b26760a4e15f Mon Sep 17 00:00:00 2001 From: Kunpeng Xie <68572236+pentaoa@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:13:42 +0800 Subject: [PATCH] fix(http): skip prefetch wait for restored downloads (#1584) --- internal/protocol/http/fetcher.go | 2 +- .../protocol/http/restore_prefetch_test.go | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 internal/protocol/http/restore_prefetch_test.go diff --git a/internal/protocol/http/fetcher.go b/internal/protocol/http/fetcher.go index 9eca0089..a693a03f 100644 --- a/internal/protocol/http/fetcher.go +++ b/internal/protocol/http/fetcher.go @@ -609,7 +609,7 @@ func (f *Fetcher) stopPrefetchAndCopyData() int64 { } // Wait for prefetch to finish (with timeout) - for i := 0; i < 1000 && !f.prefetchDone.Load(); i++ { + for i := 0; f.prefetchStopCh != nil && i < 1000 && !f.prefetchDone.Load(); i++ { time.Sleep(10 * time.Millisecond) } diff --git a/internal/protocol/http/restore_prefetch_test.go b/internal/protocol/http/restore_prefetch_test.go new file mode 100644 index 00000000..af04453e --- /dev/null +++ b/internal/protocol/http/restore_prefetch_test.go @@ -0,0 +1,79 @@ +package http + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "sync/atomic" + "testing" + "time" + + "github.com/GopeedLab/gopeed/internal/controller" + "github.com/GopeedLab/gopeed/internal/fetcher" + "github.com/GopeedLab/gopeed/internal/test" + "github.com/GopeedLab/gopeed/pkg/base" + fhttp "github.com/GopeedLab/gopeed/pkg/protocol/http" +) + +func TestFetcher_RestoredStartSkipsAbsentPrefetch(t *testing.T) { + data := bytes.Repeat([]byte("0123456789abcdef"), 64*1024) + var firstRequest atomic.Int64 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + firstRequest.CompareAndSwap(0, time.Now().UnixNano()) + http.ServeContent(w, r, "restored.bin", time.Time{}, bytes.NewReader(data)) + })) + defer server.Close() + + dir := t.TempDir() + size := int64(len(data)) + half := size / 2 + file := filepath.Join(dir, "restored.bin") + if err := os.WriteFile(file, data[:half], 0600); err != nil { + t.Fatal(err) + } + conn := &connection{ID: 0, Role: rolePrimary, State: connNotStarted, Chunk: newChunk(0, size-1), Downloaded: half} + conn.Chunk.Downloaded = half + rangeMode := true + saved := &fetcherData{Connections: []*connection{conn}, Range: &rangeMode, ResourceSize: &size, FileSize: &size} + meta := &fetcher.FetcherMeta{ + Req: &base.Request{URL: server.URL + "/restored.bin"}, + Res: &base.Resource{Size: size, Range: true, Files: []*base.FileInfo{{Name: "restored.bin", Size: size}}}, + Opts: &base.Options{Path: dir, Name: "restored.bin", Extra: &fhttp.OptsExtra{Connections: 1}}, + } + fm := new(FetcherManager) + _, restore := fm.Restore() + f := restore(meta, saved).(*Fetcher) + ctl := controller.NewController() + ctl.GetConfig = func(v any) { + if err := json.Unmarshal([]byte(test.ToJson(fm.DefaultConfig())), v); err != nil { + t.Error(err) + } + } + f.Setup(ctl) + defer f.Close() + started := time.Now() + if err := f.Start(); err != nil { + t.Fatal(err) + } + if err := f.Wait(); err != nil { + t.Fatal(err) + } + actual, err := os.ReadFile(file) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(actual, data) { + t.Fatal("restored download differs from the complete server content") + } + if firstRequest.Load() == 0 { + t.Fatal("restored download did not request the remaining content") + } + delay := time.Duration(firstRequest.Load() - started.UnixNano()) + t.Logf("first restored request after %s", delay) + if delay > 2*time.Second { + t.Errorf("restored download waited %s before its first request", delay) + } +}