Improve avatar download and verification handling with context cancellation and exponential backoff

This commit is contained in:
Leon Bösche
2026-02-01 00:50:50 +01:00
parent 96a044450f
commit a3a596bbdb

View File

@@ -4238,12 +4238,15 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
for i := 0; i < verifyRetries; i++ {
vctx, vcancel := context.WithTimeout(r.Context(), time.Duration(verifyTimeout)*time.Second)
resp, derr := internalClient.Download(vctx, avatarPath, "")
vcancel()
if derr == nil && resp != nil {
// Close body while context is still valid
resp.Body.Close()
vcancel()
verified = true
break
}
// Cancel context for failed attempt
vcancel()
fmt.Printf("[WARN] avatar verification attempt %d/%d failed for %s: %v\n", i+1, verifyRetries, avatarPath, derr)
time.Sleep(time.Duration(300*(1<<i)) * time.Millisecond) // 300ms, 600ms, 1.2s
}
@@ -4407,29 +4410,37 @@ func getUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *database.D
var resp *http.Response
var dlErr error
var cancel context.CancelFunc
for attempt := 0; attempt < attempts; attempt++ {
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(timeoutSeconds)*time.Second)
ctx, c := context.WithTimeout(r.Context(), time.Duration(timeoutSeconds)*time.Second)
cancel = c
// Use internal client to avoid external network/TLS overhead
resp, dlErr = internalClient.Download(ctx, remotePath, "")
cancel()
if dlErr == nil {
break
if dlErr != nil {
// Cancel context for failed attempt
cancel()
// If 404 on remote storage, the avatar file truly doesn't exist
if strings.Contains(dlErr.Error(), "404") {
fmt.Printf("[ERROR] Avatar not found on storage for remotePath=%s: %v\n", remotePath, dlErr)
w.WriteHeader(http.StatusNotFound)
return
}
// Log and apply backoff for retryable errors
fmt.Printf("[WARN] Avatar download attempt %d/%d failed for remotePath=%s: %v\n", attempt+1, attempts, remotePath, dlErr)
if attempt < attempts-1 {
// exponential backoff: 500ms, 1s, 2s, ...
backoffMs := 500 * (1 << attempt)
time.Sleep(time.Duration(backoffMs) * time.Millisecond)
}
continue
}
// If 404 on remote storage, the avatar file truly doesn't exist
if dlErr != nil && strings.Contains(dlErr.Error(), "404") {
fmt.Printf("[ERROR] Avatar not found on storage for remotePath=%s: %v\n", remotePath, dlErr)
w.WriteHeader(http.StatusNotFound)
return
}
// Log and apply backoff for retryable errors
fmt.Printf("[WARN] Avatar download attempt %d/%d failed for remotePath=%s: %v\n", attempt+1, attempts, remotePath, dlErr)
if attempt < attempts-1 {
// exponential backoff: 500ms, 1s, 2s, ...
backoffMs := 500 * (1 << attempt)
time.Sleep(time.Duration(backoffMs) * time.Millisecond)
}
// Success: keep the cancel func so we can call it after reading the body
break
}
if cancel != nil {
defer cancel()
}
if dlErr != nil || resp == nil {