docs: @SuppressWarnings("PMD.ExceptionAsFlowControl")
    public HttpResponse send() throws IOException {
      HttpResponse response = null;
      for (int attempt = 1; attempt <= numAttempts; ++attempt) {
        entry.withAttempt(attempt);
        try {
          response = sendImpl();
          int s = response.status();
          if (s == 429 || s == 503) {
            // Request is getting throttled, exponentially back off
            // - 429 client sending too many requests
            // - 503 server unavailable
            try {
              long delay = initialRetryDelay << (attempt - 1);
              LOGGER.debug("request throttled, delaying for {}ms: {} {}", delay, method, uri);
              Thread.sleep(delay);
            } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              throw new IOException("request failed " + method + " " + uri, e);
            }
          } else if (s < 500) {
            // 4xx errors other than 429 are not considered retriable, so for anything
            // less than 500 just return the response to the user
            return response;
          }
        } catch (IOException e) {
          // All exceptions are considered retriable. Some like UnknownHostException are
          // debatable, but we have seen them in some cases if there is a high latency for
          // DNS lookups. So for now assume all exceptions are transient issues.
          if (attempt == numAttempts) {
            throw e;
          } else {
            LOGGER.warn("attempt {} of {} failed: {} {}", attempt, numAttempts, method, uri);
          }
        }
      }
  
      if (response == null) {
        // Should not get here
        throw new IOException("request failed " + method + " " + uri);
      }
      return response;
    }