davsync tests: the slow transfer runs on a line with small buffers, and the PUT's tail is the server's to hold (TestSlowTransferIsNotCutShort red on ubuntu in PR #1030's run 37466034290: "the server sent and took nothing for 300ms (PUT)")
The test scales the watchdog from 60 s down to 300 ms and sends 20 MB at 6.4 MB/s, taking each of magpie's reads of the body as the line moving. On Linux the line's buffers aren't scaled with it: the kernel hands a writer room in bursts, waking it once a third of its send buffer has drained, and it tunes that buffer up to 4 MB (the receiver's window opens in steps the same way). A probe on magpie's body reads in a golang:1.26.3 container showed gaps of 130–290 ms between two of them while the line never stopped, every run; one over 300 ms failed the test. That is the test's ratio, not the watchdog's: at the real 60 s, a burst that long needs a line under ~25 KB/s. Not a race (the same in -race and without).
narrowLine serves the fake WebDAV server and gives the client with both ends' buffers held to 128 KB (SetWriteBuffer on the client's connection, SetReadBuffer on the server's), so a burst is a chunk or two. The MBs that used to sit in the buffers after magpie had handed the body over, which is what allSent's longer wait is for, are now the server's own tail: it answers the PUT twice the watchdog after the body is in. The server also grows its buffer to the body's length up front.
On Linux (golang:1.26.3, 14 CPUs): before, 5 of 30 runs failed (-race -count=10 -cpu 1,2,4); after, 30 of 30 pass, and -race -count=20 -cpu 1,2,8 of TestSlowTransferIsNotCutShort, TestStalledTransferFailsPromptly and TestMetadataRequestHasALimit with four busy loops beside them pass. With allSent changed to put the watchdog back by stallAfter alone, the test fails 3 of 3 ("took nothing for 300ms (PUT)"), so the tail is still tested. go test -race ./internal/davsync (darwin), go vet and gofmt -l pass. No change outside the test.
This commit is contained in:
1 parent
60f87223c3
commit
b1cba65477
1 file changed
+54
-6
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
@@ -21,10 +22,13 @@ func shortStall(t *testing.T, stall, meta time.Duration) {
|
||||
}
|
||||
|
||||
// slowDAV is a WebDAV server on a slow line: the backup goes both ways in
|
||||
// chunks of chunk bytes every gap, or stops after stopAt bytes for good.
|
||||
// chunks of chunk bytes every gap, or stops after stopAt bytes for good. A
|
||||
// PUT is answered tail after its body is all in: the time the last of it,
|
||||
// handed over by magpie, would take to cross the line.
|
||||
type slowDAV struct {
|
||||
chunk int
|
||||
gap time.Duration
|
||||
tail time.Duration
|
||||
stopAt int // 0: never
|
||||
quiet chan struct{} // closed when the test is done with a stopped server
|
||||
mu sync.Mutex
|
||||
@@ -45,6 +49,7 @@ func (s *slowDAV) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPut:
|
||||
var got bytes.Buffer
|
||||
got.Grow(int(r.ContentLength))
|
||||
buf := make([]byte, s.chunk)
|
||||
for {
|
||||
if s.stopAt > 0 && got.Len() >= s.stopAt {
|
||||
@@ -58,6 +63,7 @@ func (s *slowDAV) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
time.Sleep(s.gap)
|
||||
}
|
||||
time.Sleep(s.tail)
|
||||
s.mu.Lock()
|
||||
s.stored = got.Bytes()
|
||||
s.mu.Unlock()
|
||||
@@ -89,20 +95,62 @@ func (s *slowDAV) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// narrowLine serves h, and gives the client that reaches it, with the
|
||||
// line's buffers held small. The system hands a writer room in bursts: Linux wakes it once a third
|
||||
// of its send buffer has drained and opens the receiver's window by as much,
|
||||
// and with the MBs it tunes both up to on a fast loopback, a body moving at
|
||||
// 6.4 MB/s went 150–290 ms between two of magpie's writes, which a test's
|
||||
// watchdog of 300 ms didn't always outlast though the line never stopped (at
|
||||
// the real 60 s that is a line under ~25 KB/s). Small buffers keep the
|
||||
// bursts to a chunk or two; what sits in buffers after the body is handed
|
||||
// over is the server's tail.
|
||||
func narrowLine(t *testing.T, h http.Handler) (*httptest.Server, *http.Client) {
|
||||
const buffer = 128 << 10
|
||||
srv := httptest.NewUnstartedServer(h)
|
||||
srv.Listener = narrowListener{srv.Listener, buffer}
|
||||
srv.Start()
|
||||
t.Cleanup(srv.Close)
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
dial := tr.DialContext
|
||||
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
c, err := dial(ctx, network, addr)
|
||||
if err == nil {
|
||||
err = c.(*net.TCPConn).SetWriteBuffer(buffer)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
t.Cleanup(tr.CloseIdleConnections)
|
||||
return srv, &http.Client{Transport: stallTransport{tr}}
|
||||
}
|
||||
|
||||
type narrowListener struct {
|
||||
net.Listener
|
||||
buffer int
|
||||
}
|
||||
|
||||
func (l narrowListener) Accept() (net.Conn, error) {
|
||||
c, err := l.Listener.Accept()
|
||||
if err == nil {
|
||||
err = c.(*net.TCPConn).SetReadBuffer(l.buffer)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// A backup that takes far longer than the watchdog to go up and come back
|
||||
// down, but never stops moving, is written and read in full (#657: 43 MB
|
||||
// at 305 KB/s took 2.4 minutes, and a sync was given 2).
|
||||
func TestSlowTransferIsNotCutShort(t *testing.T) {
|
||||
shortStall(t, 300*time.Millisecond, 300*time.Millisecond)
|
||||
s := &slowDAV{chunk: 256 << 10, gap: 40 * time.Millisecond}
|
||||
srv := httptest.NewServer(s)
|
||||
defer srv.Close()
|
||||
// the PUT's last MBs cross the line twice the watchdog after magpie has
|
||||
// handed them all over
|
||||
s := &slowDAV{chunk: 256 << 10, gap: 40 * time.Millisecond, tail: 2 * stallAfter}
|
||||
srv, client := narrowLine(t, s)
|
||||
d, err := newDAV(Config{URL: srv.URL})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// ~20 MB at 6.4 MB/s: 3 s each way, the last MBs of the PUT sitting in
|
||||
// the system's buffers long after magpie handed them over
|
||||
d.client = client
|
||||
// ~20 MB at 6.4 MB/s: 3 s each way
|
||||
data := bytes.Repeat([]byte("magpie skill "), (20<<20)/13)
|
||||
ctx := context.Background()
|
||||
start := time.Now()
|
||||
|
||||
Reference in new issue
Block a user