mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 23:38:50 +02:00
Stability: - transient timeouts no longer classified as ISP blocking; they fall through to retry backoff (hard blocks - DNS/RST/cert - still abort) - connectivity change now closes idle Go connections in every network mode (debounced), so pooled sockets from the old interface are not reused after a wifi/cellular handoff - download body reads get a 60s stall watchdog that cancels and surfaces a retryable network error instead of hanging; distinct from user cancellation - ResponseHeaderTimeout 45s on all transports; IdleConnTimeout 90->60s; dial timeout 30->10s; retry backoff gains full jitter; Retry-After honored on 5xx and Deezer 429 Data usage / speed: - cover downloads deduplicated with singleflight plus a 24MB/15min in-memory cache keyed by final URL (album batches fetched the same 1800px cover once per track before) - song.link availability cached (30min positive / 5min negative) in front of the 9-req/min rate limiter - uTLS Cloudflare path now pools one HTTP/2 connection per host with a shared TLS session cache instead of a full handshake per request Deezer artist track-count N+1 kept: counts feed the discography download UI and are already amortized by the artist TTL cache.
55 lines
1.3 KiB
Go
55 lines
1.3 KiB
Go
package gobackend
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestStallWatchdogCancelsOnNoData verifies the watchdog aborts a transfer that
|
|
// stops sending bytes, marks itself stalled (not user-cancelled), and does so
|
|
// after resetting on the initial byte.
|
|
func TestStallWatchdogCancelsOnNoData(t *testing.T) {
|
|
block := make(chan struct{})
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Length", "1000")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("x"))
|
|
w.(http.Flusher).Flush()
|
|
<-block // simulate a dead radio mid-transfer: stop sending
|
|
}))
|
|
defer srv.Close()
|
|
defer close(block)
|
|
|
|
req, _ := http.NewRequestWithContext(context.Background(), "GET", srv.URL, nil)
|
|
req, wd := bindStallWatchdog(req, 150*time.Millisecond)
|
|
defer wd.stop()
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("do: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
buf := make([]byte, 32)
|
|
var readErr error
|
|
for {
|
|
n, er := resp.Body.Read(buf)
|
|
if n > 0 {
|
|
wd.reset()
|
|
}
|
|
if er != nil {
|
|
readErr = er
|
|
break
|
|
}
|
|
}
|
|
if readErr == nil {
|
|
t.Fatal("expected read error from stall cancel")
|
|
}
|
|
if !wd.stalled.Load() {
|
|
t.Fatalf("watchdog did not mark stalled; err=%v", readErr)
|
|
}
|
|
}
|