module Ecoportal module API module Common class Client module WithRetry DELAY_REQUEST_RETRY = 5 RETRY_ATTEMPTS = 5 HANDLED_CONNECTION_ERRORS = [ HTTP::ConnectionError, IOError ].freeze include Ecoportal::API::Common::Client::ElasticApmIntegration private # Helper to ensure unexpected server errors do not bring # client scripts immediately down # @note it manages limited range of errors, the rest # are not handled. def with_retry( attempts = retry_attemps, delay = delay_request_retry, error_safe: true, &block ) response = nil attempts.times do |i| remaining = attempts - i - 1 response = with_connection_error_handling( remaining, error_safe: error_safe, callback: block ) do block.call end return response unless some_unexpected_error?(response) # handle server errors (5xx) & server bugs (i.e. empty body) msg = "re-attempting (remaining: " msg << "#{remaining} attempts out of #{attempts})" log(:debug) { msg } log_unexpected_server_error(response) msg = "Got server error (#{response.status}): #{response.body}\n" msg << "Going to retry (##{i + 1} of #{attempts})" log(:debug) { msg } sleep(delay) if i < attempts end response end def with_connection_error_handling(remaining, callback:, error_safe: true) yield rescue *handled_connection_errors => err raise unless error_safe && remaining.positive? msg = "Got #{err.class}: #{err.message}" log(:debug) { msg } with_retry(remaining, error_safe: error_safe, &callback) end # Add here other connection errors def handled_connection_errors self.class::HANDLED_CONNECTION_ERRORS end def retry_attemps self.class::RETRY_ATTEMPTS end def delay_request_retry self.class::DELAY_REQUEST_RETRY end end end end end end