public void perform(List<Exception> list, T obj) {
    do {
      try {
        op.operation(list);
        return;
      } catch (Exception e) {
        this.errors.add(e);
        if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) {
          this.handleError.handleIssue(obj, e);
          return; //return here... don't go further
        }
        try {
          long testDelay =
              (long) Math.pow(2, this.attempts.intValue()) * 1000 + RANDOM.nextInt(1000);
          long delay = Math.min(testDelay, this.maxDelay);
          Thread.sleep(delay);
        } catch (InterruptedException f) {
          //ignore
        }
      }
    } while (true);
  }
@Test
  void performTest() {
    Retry.Operation op = (l) -> {
      if (!l.isEmpty()) {
        throw l.remove(0);
      }
    };
    Retry.HandleErrorIssue<Order> handleError = (o, e) -> {
    };
    var r1 = new Retry<>(op, handleError, 3, 30000,
        e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
    var r2 = new Retry<>(op, handleError, 3, 30000,
        e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
    var user = new User("Jim", "ABCD");
    var order = new Order(user, "book", 10f);
    var arr1 = new ArrayList<>(List.of(new ItemUnavailableException(), new DatabaseUnavailableException()));
    try {
      r1.perform(arr1, order);
    } catch (Exception e1) {
      LOG.error("An exception occurred", e1);
    }
    var arr2 = new ArrayList<>(List.of(new DatabaseUnavailableException(), new ItemUnavailableException()));
    try {
      r2.perform(arr2, order);
    } catch (Exception e1) {
      LOG.error("An exception occurred", e1);
    }
    //r1 stops at ItemUnavailableException, r2 retries because it encounters DatabaseUnavailableException
    assertTrue(arr1.size() == 1 && arr2.isEmpty());
  }