mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-08 17:40:28 +00:00
fix(http): copy a fully prefetched body when Start runs before prefetchDone (#1608)
The prefetch goroutine sets prefetchDone only after one more read and closing the body. A Start in that window took the whole file as the fallback snapshot, expandConnections marked the task done, and the temp file was never copied, leaving a zero-filled target. Read prefetchSize once and treat the prefetch as complete when it reached the known size.
This commit is contained in:
1 parent
9e9bcd4b01
commit
d873cd411c
2 files changed
+58
-2
No files matched your search
@@ -872,12 +872,16 @@ func (f *Fetcher) doStart() error {
|
||||
// For non-range resources, the response will be used directly
|
||||
var prefetchedBytes int64
|
||||
if f.meta.Res.Range {
|
||||
resolveComplete := f.knownSize() > 0 && f.prefetchDone.Load() && f.prefetchErr == nil && f.prefetchSize.Load() >= f.knownSize()
|
||||
// Read the size once and judge completeness by it alone: Resolve may have
|
||||
// written every byte before it sets prefetchDone, and a snapshot that
|
||||
// covers the whole file would otherwise finish without copying it.
|
||||
prefetched := f.prefetchSize.Load()
|
||||
resolveComplete := f.knownSize() > 0 && prefetched >= f.knownSize()
|
||||
if f.resolveFallback.Load() && !resolveComplete {
|
||||
// Keep Resolve downloading until the first Range response has been
|
||||
// validated. The Range request starts at a stable snapshot; a valid
|
||||
// takeover discards any overlap downloaded by Resolve in the meantime.
|
||||
prefetchedBytes = f.prefetchSize.Load()
|
||||
prefetchedBytes = prefetched
|
||||
} else {
|
||||
// Resolve already completed, or no live Resolve fallback remains.
|
||||
prefetchedBytes = f.stopPrefetchAndCopyData()
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gohttp "net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||
)
|
||||
|
||||
// Start can run after the Resolve prefetch wrote every byte but before it set
|
||||
// prefetchDone. The prefetched body must still be copied to the target.
|
||||
func TestFetcher_StartBeforePrefetchDoneCopiesFullBody(t *testing.T) {
|
||||
payload := []byte("prefetched before Start")
|
||||
server := httptest.NewServer(gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) {
|
||||
gohttp.ServeContent(w, r, "f.txt", time.Time{}, bytes.NewReader(payload))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
f := buildFetcher()
|
||||
f.Meta().Opts = &base.Options{Path: t.TempDir(), Name: "f.txt", Extra: &http.OptsExtra{Connections: 1}}
|
||||
if err := f.Resolve(&base.Request{URL: server.URL + "/f.txt"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for !f.prefetchDone.Load() {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
// Reopen the window between the last write and prefetchDone.
|
||||
f.prefetchDone.Store(false)
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
f.prefetchDone.Store(true)
|
||||
}()
|
||||
|
||||
if err := f.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := os.ReadFile(f.meta.SingleFilepath())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, payload) {
|
||||
t.Fatalf("content = %q, want %q", got, payload)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user