mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 10:00:27 +00:00
* fix(http): wait for the prefetch goroutine to exit before reusing its file stopPrefetchAndCopyData waited at most 10 s for the Resolve prefetch, but a stalled body read only times out after readTimeout (15 s). The caller then closed and removed the temporary file while the prefetch goroutine could still write to it. Stopping the prefetch now closes the Resolve body, which interrupts a pending read, and waits on a channel closed when the goroutine exits. Pause uses the same path. The stop channel is closed under resolveRespLock so a concurrent Pause and first-connection takeover cannot close it twice. The proxy test helper now calls Setup before Resolve, as the downloader does, so the Resolve request also goes through the proxy. Fixes #1563 * refactor(http): wait on prefetchExitCh in completeFromResolveFallback Replaces the 10 ms polling loop with the exit channel added in the previous commit, so every place that waits for the prefetch uses it. --------- Co-authored-by: Levi <[email protected]>
57 lines
1.5 KiB
Go
57 lines
1.5 KiB
Go
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
gohttp "net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
|
)
|
|
|
|
// A stalled Resolve body must not keep the prefetch goroutine alive after
|
|
// stopPrefetchAndCopyData returns, or it writes to a file we already removed.
|
|
func TestFetcher_StopPrefetchUnblocksStalledRead(t *testing.T) {
|
|
prefix := bytes.Repeat([]byte("p"), 64*1024)
|
|
stall := make(chan struct{})
|
|
defer close(stall)
|
|
|
|
server := httptest.NewServer(gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) {
|
|
w.Header().Set(base.HttpHeaderAcceptRanges, base.HttpHeaderBytes)
|
|
w.Header().Set(base.HttpHeaderContentLength, fmt.Sprintf("%d", len(prefix)*4))
|
|
w.WriteHeader(gohttp.StatusOK)
|
|
_, _ = w.Write(prefix)
|
|
w.(gohttp.Flusher).Flush()
|
|
select {
|
|
case <-stall:
|
|
case <-r.Context().Done():
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
f := buildFetcher()
|
|
f.Meta().Opts = &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "stalled.data",
|
|
Extra: &http.OptsExtra{Connections: 1},
|
|
}
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/stalled.data"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for f.prefetchSize.Load() < int64(len(prefix)) {
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
|
|
start := time.Now()
|
|
f.stopPrefetch()
|
|
if elapsed := time.Since(start); elapsed > 2*time.Second {
|
|
t.Fatalf("stopPrefetch took %v, want it to interrupt the stalled read", elapsed)
|
|
}
|
|
if !f.prefetchDone.Load() {
|
|
t.Fatal("prefetch goroutine still running after stopPrefetch")
|
|
}
|
|
}
|