cmd/cigocacher,go.mod: add logging for canceled PUTs (#20584)

Pulls in bradfitz/go-tool-cache#43 and:

* Add logging for canceled PUTs to ensure we have some visibility.
* Scale PUT timeouts with object size.
* Control the Shutdown timeout separately from the PUT timeout.

Updates tailscale/corp#45334

Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
Tom Proctor
2026-07-23 20:15:41 +01:00
committed by GitHub
parent ac33a4cc1a
commit 682005aaa6
7 changed files with 60 additions and 9 deletions
+28 -2
View File
@@ -149,7 +149,7 @@ func main() {
AccessToken: *token,
Verbose: *verbose,
BestEffortHTTP: true,
AsyncPutTimeout: 10 * time.Second, // Generous enough to allow for big objects ~100MiB.
AsyncPutTimeout: asyncPutTimeout,
AsyncPutMaxConcurrent: 10,
}
}
@@ -157,9 +157,15 @@ func main() {
p = &cacheproc.Process{
Close: func() error {
if c.remote != nil {
if !c.remote.Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if !c.remote.Shutdown(ctx) {
log.Printf("cigocacher: timed out waiting for background PUTs to drain")
}
// Always surface dropped PUTs.
if timedOut, canceled := c.remote.PutsTimedOut.Load(), c.remote.PutsCanceled.Load(); timedOut+canceled > 0 {
log.Printf("cigocacher: %d background PUTs timed out, %d canceled", timedOut, canceled)
}
}
if c.verbose {
log.Printf("cigocacher: closing; %d gets (%d hits, %d misses, %d errors); %d puts (%d errors)",
@@ -345,3 +351,23 @@ func fetchStats(cl *http.Client, baseURL, accessToken string) (string, error) {
}
return string(b), nil
}
const (
// minPutTimeout is the floor we clamp to for small objects where the time is
// dominated by fixed overheads like connection establishment, waiting for a
// busy server to service the request etc.
minPutTimeout = 5 * time.Second
// maxPutTimeout is the ceiling we clamp to for large objects.
maxPutTimeout = 30 * time.Second
// minAverageBandwidth is the minimum average bandwidth (2MiB/s) we require
// for PUTs to complete within the timeout in its linear scaling region.
minAverageBandwidth = 2 * 1 << 20 / float64(time.Second)
)
// asyncPutTimeout returns a size-dependent timeout for async PUTs to the remote
// gocached server. It returns 5s for size <= 10MiB, 30s for size >= 60MiB and
// scales linearly in between.
func asyncPutTimeout(size int64) time.Duration {
timeout := time.Duration(float64(size) / minAverageBandwidth)
return min(max(minPutTimeout, timeout), maxPutTimeout)
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"testing"
"time"
)
func TestAsyncPutTimeout(t *testing.T) {
for size, expected := range map[int64]time.Duration{
0: 5 * time.Second,
10: 5 * time.Second,
10 * 1 << 20: 5 * time.Second,
20 * 1 << 20: 10 * time.Second,
40 * 1 << 20: 20 * time.Second,
60 * 1 << 20: 30 * time.Second,
10 * 1 << 30: 30 * time.Second,
} {
if actual := asyncPutTimeout(size); actual != expected {
t.Errorf("for size %d, expected %v, but got %v", size, expected, actual)
}
}
}