mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 08:30:29 +00:00
fix(http): reset discarded prefetch progress on resume (#1462)
This commit is contained in:
6 files changed
+93
-9
No files matched your search
@@ -91,7 +91,7 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
- name: Test webview
|
||||
if: runner.os == 'Windows'
|
||||
run: go test -tags webview -v -timeout 300s ./internal/webview/goprovider/
|
||||
run: go test -tags webview -v -count=1 -timeout 300s ./internal/webview/goprovider/
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
- name: Test webview
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
cd ui/flutter
|
||||
flutter pub get
|
||||
flutter run -d macos >"$LOG_PATH" 2>&1 &
|
||||
flutter run --machine -d macos >"$LOG_PATH" 2>&1 &
|
||||
FLUTTER_PID=$!
|
||||
cd ../..
|
||||
|
||||
@@ -116,7 +116,26 @@ jobs:
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if ! go test -tags webview -v -timeout 600s ./internal/webview/rpcprovider/ -args -webview-rpc-network unix -webview-rpc-address "$SOCKET_PATH"; then
|
||||
FLUTTER_STARTED=false
|
||||
for _ in $(seq 1 600); do
|
||||
if grep -Eq '"event"[[:space:]]*:[[:space:]]*"app\.started"' "$LOG_PATH"; then
|
||||
FLUTTER_STARTED=true
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "$FLUTTER_PID" >/dev/null 2>&1; then
|
||||
echo "Flutter exited before reporting app.started"
|
||||
cat "$LOG_PATH"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [[ "$FLUTTER_STARTED" != true ]]; then
|
||||
echo "Timed out waiting for Flutter to report app.started"
|
||||
cat "$LOG_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! go test -tags webview -v -count=1 -timeout 600s ./internal/webview/rpcprovider/ -args -webview-rpc-network unix -webview-rpc-address "$SOCKET_PATH"; then
|
||||
cat "$LOG_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -584,6 +584,10 @@ func (f *Fetcher) cleanupPrefetchFile() {
|
||||
os.Remove(f.prefetchFilePath)
|
||||
f.prefetchFilePath = ""
|
||||
}
|
||||
// The byte count is only reusable while the backing prefetch file exists.
|
||||
// Pause may discard that file before the first Start, so retaining the
|
||||
// count would make the next Start skip bytes that were never copied.
|
||||
f.prefetchSize.Store(0)
|
||||
}
|
||||
|
||||
func (f *Fetcher) Start() error {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -377,6 +379,59 @@ func TestFetcher_DownloadContinue(t *testing.T) {
|
||||
downloadContinue(listener, 16, t)
|
||||
}
|
||||
|
||||
func TestFetcher_PauseBeforeStartDiscardsPrefetchProgress(t *testing.T) {
|
||||
payload := make([]byte, 32*1024*1024)
|
||||
for i := range payload {
|
||||
payload[i] = byte(i % 251)
|
||||
}
|
||||
prefetchServed := make(chan struct{}, 1)
|
||||
server := httptest.NewServer(gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) {
|
||||
if r.Header.Get(base.HttpHeaderRange) == "" {
|
||||
w.Header().Set(base.HttpHeaderAcceptRanges, base.HttpHeaderBytes)
|
||||
w.Header().Set(base.HttpHeaderContentLength, fmt.Sprintf("%d", len(payload)))
|
||||
w.WriteHeader(gohttp.StatusOK)
|
||||
if _, err := w.Write(payload); err == nil {
|
||||
prefetchServed <- struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
gohttp.ServeContent(w, r, test.BuildName, time.Unix(0, 0), bytes.NewReader(payload))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
fetcher := buildFetcher()
|
||||
if err := fetcher.Resolve(&base.Request{URL: server.URL + "/" + test.BuildName}, &base.Options{
|
||||
Path: t.TempDir(),
|
||||
Name: test.DownloadName,
|
||||
Extra: &http.OptsExtra{
|
||||
Connections: 2,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case <-prefetchServed:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timed out waiting for resolve prefetch response")
|
||||
}
|
||||
|
||||
if err := fetcher.Pause(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := fetcher.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := fetcher.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := fmt.Sprintf("%x", md5.Sum(payload))
|
||||
got := test.FileMd5(fetcher.Meta().SingleFilepath())
|
||||
if want != got {
|
||||
t.Fatalf("download after pause before start got MD5 %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetcher_DownloadContinue_NoRangeRestart(t *testing.T) {
|
||||
listener := test.StartTestNoRangeSlowServer(time.Millisecond)
|
||||
defer listener.Close()
|
||||
|
||||
@@ -81,6 +81,10 @@ type nativeCookieJSON struct {
|
||||
|
||||
var providerSeq atomic.Uint64
|
||||
|
||||
// Keep native page creation aligned with the provider availability budget used
|
||||
// by the integration contract. Cold WebView2 startup can exceed ten seconds.
|
||||
const startupTimeout = time.Minute
|
||||
|
||||
func newPageWrapper(opts enginewebview.OpenOptions) *pageWrapper {
|
||||
seq := providerSeq.Add(1)
|
||||
return &pageWrapper{
|
||||
@@ -142,7 +146,7 @@ func (p *pageWrapper) start() error {
|
||||
case err := <-p.ready:
|
||||
p.tracef("open %dms", time.Since(startedAt).Milliseconds())
|
||||
return err
|
||||
case <-time.After(10 * time.Second):
|
||||
case <-time.After(startupTimeout):
|
||||
p.tracef("open timeout %dms", time.Since(startedAt).Milliseconds())
|
||||
return fmt.Errorf("webview startup timeout")
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ import (
|
||||
|
||||
const TestUserAgent = "GopeedWebViewIntegration/1.0"
|
||||
|
||||
// Providers may need to initialize an external host or a native browser runtime
|
||||
// before the first page can be created.
|
||||
const defaultAvailabilityTimeout = time.Minute
|
||||
|
||||
type ContractOptions struct {
|
||||
AvailabilityTimeout time.Duration
|
||||
CookieDomainMode CookieDomainMode
|
||||
@@ -32,7 +36,7 @@ func RunProviderContract(t *testing.T, provider enginewebview.Provider, opts Con
|
||||
|
||||
timeout := opts.AvailabilityTimeout
|
||||
if timeout <= 0 {
|
||||
timeout = 10 * time.Second
|
||||
timeout = defaultAvailabilityTimeout
|
||||
}
|
||||
waitForProviderAvailable(t, provider, timeout)
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/internal/webview/integrationtest"
|
||||
enginewebview "github.com/GopeedLab/gopeed/pkg/download/engine/webview"
|
||||
@@ -26,9 +25,8 @@ func TestProviderContract(t *testing.T) {
|
||||
Token: *webViewRPCToken,
|
||||
})
|
||||
integrationtest.RunProviderContract(t, provider, integrationtest.ContractOptions{
|
||||
AvailabilityTimeout: 5 * time.Minute,
|
||||
CookieDomainMode: integrationtest.CookieDomainModeRequired,
|
||||
CookieTestURL: "https://example.com/",
|
||||
CookieDomainMode: integrationtest.CookieDomainModeRequired,
|
||||
CookieTestURL: "https://example.com/",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user