Callback { public synchronized void runWithRetry(R resource) { while (!shutdownSignal) { try { if (!shutdownSignal) { init(resource); } return; } catch (Throwable e) { cleanup(resource, e); } } }  abstract void init(R resource); void cleanup(R resource, Throwable initException); synchronized void runWithRetry(R resource); synchronized boolean runOnceOnly(R resource); void blockUntilSafeToShutdown(); static Callback<R> noOp();  }
@Test public void runWithRetryStopsRetryingOnSuccess() { CountingCallback countingCallback = new CountingCallback(false); AtomicLong counter = new AtomicLong(0); countingCallback.runWithRetry(counter); assertThat(counter.get()).isEqualTo(10L); }
@Test public void cleanupExceptionGetsPropagatedAndStopsRetrying() { CountingCallback countingCallback = new CountingCallback(true); AtomicLong counter = new AtomicLong(0); assertThatThrownBy(() -> countingCallback.runWithRetry(counter)) .isInstanceOf(RuntimeException.class) .hasMessageContaining("LEGIT REASON"); assertThat(counter.get()).isEqualTo(5L); }