diff options
| author | Bobby <[email protected]> | 2025-04-18 19:38:25 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-04-18 19:38:25 +0530 |
| commit | efce17623215f4f2183c66a832b23a14ee662880 (patch) | |
| tree | 52a3b75ada7da8a4ce13e233f7b89cdff498b312 /utils | |
| parent | 5dc2f648cf3d9634cfb8763cebc6f1dec4042914 (diff) | |
| download | metachan-efce17623215f4f2183c66a832b23a14ee662880.tar.xz metachan-efce17623215f4f2183c66a832b23a14ee662880.zip | |
make tmdb request with retries for handling temporary network failures
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/anime/tmdb.go | 104 |
1 files changed, 80 insertions, 24 deletions
diff --git a/utils/anime/tmdb.go b/utils/anime/tmdb.go index d4d22a0..3f3df24 100644 --- a/utils/anime/tmdb.go +++ b/utils/anime/tmdb.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math" + "math/rand" "metachan/config" "metachan/types" "metachan/utils/logger" @@ -12,6 +13,76 @@ import ( "time" ) +// makeRequestWithRetries executes an HTTP request with retries for handling temporary network failures +func makeRequestWithRetries(req *http.Request, maxRetries int) (*http.Response, error) { + client := &http.Client{ + Timeout: 10 * time.Second, + } + + var lastErr error + for attempt := 0; attempt <= maxRetries; attempt++ { + if attempt > 0 { + // Exponential backoff with jitter for retries + backoffTime := time.Duration(math.Pow(1.5, float64(attempt))) * time.Second + + // Updated jitter calculation without using deprecated rand.Seed + jitter := time.Duration(rand.Int31n(500)) * time.Millisecond + + sleepTime := backoffTime + jitter + + logger.Log(fmt.Sprintf("TMDB request retry %d/%d after %v due to: %v", + attempt, maxRetries, sleepTime, lastErr), types.LogOptions{ + Level: types.Debug, + Prefix: "TMDB", + }) + + time.Sleep(sleepTime) + + // Create a fresh request to avoid any issues with reusing the same request + newReq, err := http.NewRequest(req.Method, req.URL.String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to create new request for retry: %w", err) + } + + // Copy all headers from the original request + for key, values := range req.Header { + for _, value := range values { + newReq.Header.Add(key, value) + } + } + + // Set the new retry request as our active request + req = newReq + } + + resp, err := client.Do(req) + if err != nil { + lastErr = err + // Check if this is a network error that might be temporary + if strings.Contains(err.Error(), "connection reset by peer") || + strings.Contains(err.Error(), "EOF") || + strings.Contains(err.Error(), "connection refused") || + strings.Contains(err.Error(), "timeout") { + // These are retryable errors + continue + } + // Other errors are not retryable + return nil, err + } + + // If we got a server error (5xx), retry + if resp.StatusCode >= 500 && resp.StatusCode < 600 { + lastErr = fmt.Errorf("server error: %s", resp.Status) + resp.Body.Close() // Make sure we close the body before we retry + continue + } + + return resp, nil + } + + return nil, fmt.Errorf("failed after %d retries: %w", maxRetries, lastErr) +} + // normalizeTitle cleans up the anime title for better matching with TMDB func normalizeTitle(title string) string { // Handle empty titles @@ -80,15 +151,10 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.Config.TMDB.ReadAccessToken)) req.Header.Add("Accept", "application/json") - // Create HTTP client with timeout - client := &http.Client{ - Timeout: 10 * time.Second, - } - - // Execute request - resp, err := client.Do(req) + // Use our retry mechanism (3 retries) + resp, err := makeRequestWithRetries(req, 3) if err != nil { - return nil, fmt.Errorf("failed to execute request: %w", err) + return nil, fmt.Errorf("failed to search TV shows: %w", err) } defer resp.Body.Close() @@ -168,15 +234,10 @@ func getTVShowDetails(showID int) (*types.TMDBShowDetails, error) { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.Config.TMDB.ReadAccessToken)) req.Header.Add("Accept", "application/json") - // Create HTTP client with timeout - client := &http.Client{ - Timeout: 10 * time.Second, - } - - // Execute request - resp, err := client.Do(req) + // Use our retry mechanism (3 retries) + resp, err := makeRequestWithRetries(req, 3) if err != nil { - return nil, fmt.Errorf("failed to execute request: %w", err) + return nil, fmt.Errorf("failed to get TV show details: %w", err) } defer resp.Body.Close() @@ -209,15 +270,10 @@ func getSeasonDetails(showID, seasonNumber int) (*types.TMDBSeasonDetails, error req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.Config.TMDB.ReadAccessToken)) req.Header.Add("Accept", "application/json") - // Create HTTP client with timeout - client := &http.Client{ - Timeout: 10 * time.Second, - } - - // Execute request - resp, err := client.Do(req) + // Use our retry mechanism (3 retries) + resp, err := makeRequestWithRetries(req, 3) if err != nil { - return nil, fmt.Errorf("failed to execute request: %w", err) + return nil, fmt.Errorf("failed to get season details: %w", err) } defer resp.Body.Close() |
