module Backup::Backblaze::Retry

Constants

MAX_RETRIES

Public Instance Methods

call(retries, backoff, api_call_name, &blk) click to toggle source
# File lib/backup/backblaze/retry.rb, line 31
                def call retries, backoff, api_call_name, &blk
  raise TooManyRetries, "max tries is #{MAX_RETRIES}" unless retries < MAX_RETRIES

  # default exponential backoff for retries > 0
  backoff ||= retries ** 2

  # minor avoidance of unnecessary work in sleep if there's no backoff needed.
  if backoff > 0
    ::Backup::Logger.info "calling #{api_call_name} retry #{retries} after sleep #{backoff}"
    sleep backoff
  else
    ::Backup::Logger.info "calling #{api_call_name}"
  end

  # Finally! Do the call.
  blk.call

rescue Excon::Errors::HTTPStatusError => ex
  Backup::Logger.info ex.message
  # backoff can end up nil, if Retry-After isn't specified.
  backoff = ex.response.headers['Retry-After']&.to_i
  ::Backup::Logger.info "server specified Retry-After of #{backoff.inspect}"
  raise "Retry-After #{backoff} > 60 is too long" if backoff && backoff > 60

  # need to get code from body
  body_wrap = HashWrap.from_json ex.response.body

  # figure out which retry sequence to use
  recovery_sequence = RetryLookup.retry_sequence api_call_name, ex.response.status, body_wrap.code

  # There's a sequence of retries, and we don't know how to hook the
  # return values and parameters together. So make that someone else's
  # problem.
  #
  # TODO possibly just execute the retry sequence here?
  # That's quite hard cos it will have to have access to the calling self
  if recovery_sequence.any?
    ::Backup::Logger.info "recovery sequence of #{recovery_sequence.inspect}"
    raise RetrySequence.new(recovery_sequence, backoff)
  else
    raise
  end

rescue Excon::Errors::Error => ex
  Backup::Logger.info ex.message
  # Socket errors etc therefore no http status code and no response body.
  # So just retry with default exponential backoff.
  call retries + 1, nil, api_call_name, &blk
end