mirror of
https://github.com/karust/openserp.git
synced 2026-08-11 03:21:16 +08:00
- replace proxy wiring with global and per-engine tag policies - split stats endpoints and lock fail-closed proxy behavior with tests
101 lines
2.3 KiB
Go
101 lines
2.3 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// RetryConfig controls retry behavior.
|
|
type RetryConfig struct {
|
|
MaxRetries int
|
|
InitialBackoff time.Duration
|
|
MaxBackoff time.Duration
|
|
BackoffFactor float64
|
|
}
|
|
|
|
func DefaultRetryConfig() RetryConfig {
|
|
return RetryConfig{
|
|
MaxRetries: 3,
|
|
InitialBackoff: time.Second,
|
|
MaxBackoff: 30 * time.Second,
|
|
BackoffFactor: 2.0,
|
|
}
|
|
}
|
|
|
|
type RetryResult struct {
|
|
Results []SearchResult
|
|
Err error
|
|
Attempts int
|
|
Engine string
|
|
}
|
|
|
|
// RetryableSearch executes searchFn with exponential backoff retries.
|
|
// CAPTCHA and proxy-unavailable errors are not retried.
|
|
func RetryableSearch(cfg RetryConfig, engineName string, searchFn func() ([]SearchResult, error)) RetryResult {
|
|
if cfg.BackoffFactor <= 0 {
|
|
cfg.BackoffFactor = 2.0
|
|
}
|
|
|
|
var lastErr error
|
|
for attempt := 0; attempt <= cfg.MaxRetries; attempt++ {
|
|
if attempt > 0 {
|
|
backoff := calculateBackoff(cfg, attempt)
|
|
logrus.Warnf("[%s] Retry attempt %d/%d after %s", engineName, attempt, cfg.MaxRetries, backoff)
|
|
time.Sleep(backoff)
|
|
}
|
|
|
|
results, err := searchFn()
|
|
if err == nil {
|
|
if attempt > 0 {
|
|
logrus.Infof("[%s] Succeeded on retry attempt %d", engineName, attempt)
|
|
}
|
|
return RetryResult{
|
|
Results: results,
|
|
Attempts: attempt + 1,
|
|
Engine: engineName,
|
|
}
|
|
}
|
|
|
|
lastErr = err
|
|
if errors.Is(err, ErrCaptcha) {
|
|
logrus.Warnf("[%s] CAPTCHA detected, skipping retries", engineName)
|
|
return RetryResult{
|
|
Err: err,
|
|
Attempts: attempt + 1,
|
|
Engine: engineName,
|
|
}
|
|
}
|
|
if errors.Is(err, ErrProxyUnavailable) {
|
|
logrus.Warnf("[%s] Proxy unavailable, skipping retries", engineName)
|
|
return RetryResult{
|
|
Err: err,
|
|
Attempts: attempt + 1,
|
|
Engine: engineName,
|
|
}
|
|
}
|
|
|
|
logrus.Warnf("[%s] Attempt %d failed: %s", engineName, attempt+1, err)
|
|
}
|
|
|
|
return RetryResult{
|
|
Err: fmt.Errorf("all %d attempts failed for %s: %w", cfg.MaxRetries+1, engineName, lastErr),
|
|
Attempts: cfg.MaxRetries + 1,
|
|
Engine: engineName,
|
|
}
|
|
}
|
|
|
|
func calculateBackoff(cfg RetryConfig, attempt int) time.Duration {
|
|
backoff := float64(cfg.InitialBackoff) * math.Pow(cfg.BackoffFactor, float64(attempt-1))
|
|
if backoff > float64(cfg.MaxBackoff) {
|
|
backoff = float64(cfg.MaxBackoff)
|
|
}
|
|
if backoff < 0 {
|
|
backoff = 0
|
|
}
|
|
return time.Duration(backoff)
|
|
}
|