Module: TempmailSdk::Retry

Defined in:
lib/tempmail_sdk/retry.rb

Overview

通用重试工具 提供请求重试、超时控制、指数退避等错误恢复机制

Defined Under Namespace

Classes: AttemptsResult

Constant Summary collapse

NETWORK_KEYWORDS =

网络级别错误关键字

[
  "connection", "timeout", "timed out", "dns", "eof",
  "broken pipe", "network is unreachable", "refused", "reset"
].freeze

Class Method Summary collapse

Class Method Details

.should_retry?(error) ⇒ Boolean

判断错误是否应该重试 触发重试:网络错误、429 限流、HTTP 4xx/5xx;仅参数校验类错误不重试

Parameters:

  • error (Exception)

Returns:

  • (Boolean)


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/tempmail_sdk/retry.rb', line 35

def should_retry?(error)
  msg = error.message.to_s.downcase
  return true if NETWORK_KEYWORDS.any? { |kw| msg.include?(kw) }
  return true if msg.include?("429") || msg.include?("too many requests") || msg.include?("rate limit")
  return true if msg.include?(": 4") || msg.include?(": 5")

  # Net::HTTP 底层网络异常类型
  case error
  when Timeout::Error, Errno::ECONNREFUSED, Errno::ECONNRESET,
       SocketError, EOFError, IOError
    true
  else
    false
  end
end

.with_retry(config = nil) { ... } ⇒ Object

带重试的操作执行器(失败抛异常版本)

Parameters:

Yields:

  • 要执行的操作



84
85
86
87
88
89
# File 'lib/tempmail_sdk/retry.rb', line 84

def with_retry(config = nil, &block)
  r = with_retry_with_attempts(config, &block)
  return r.value if r.ok?

  raise r.error
end

.with_retry_with_attempts(config = nil) { ... } ⇒ AttemptsResult

带重试的操作执行器(不抛异常版本)

Parameters:

Yields:

  • 要执行的操作

Returns:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/tempmail_sdk/retry.rb', line 55

def with_retry_with_attempts(config = nil)
  cfg = config || RetryConfig.new
  logger = Log.logger
  last_error = nil

  (0..cfg.max_retries).each do |attempt|
    attempts = attempt + 1
    begin
      result = yield
      logger.info("#{attempt + 1} 次尝试成功") if attempt.positive?
      return AttemptsResult.new(ok: true, attempts: attempts, value: result)
    rescue StandardError => e
      last_error = e
      if attempt >= cfg.max_retries || !should_retry?(e)
        return AttemptsResult.new(ok: false, attempts: attempts, error: e)
      end

      delay = [cfg.initial_delay * (2**attempt), cfg.max_delay].min
      logger.warn("请求失败 (#{e.message}),#{format('%.1f', delay)}s 后第 #{attempt + 2} 次重试...")
      sleep(delay)
    end
  end

  AttemptsResult.new(ok: false, attempts: cfg.max_retries + 1, error: last_error)
end