Improve avatar download and verification handling with context cancellation and exponential backoff
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user