package org.rnorth.ducttape.inconsistents; import org.jetbrains.annotations.NotNull; import org.rnorth.ducttape.unreliables.Unreliables; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.rnorth.ducttape.Preconditions.check; /** * Utility for calling a supplier that may take time to stabilise on a final result. */ public class Inconsistents { /** * Retry invocation of a supplier repeatedly until it returns a consistent result for a sufficient time period. * * This is intended for calls to components that take an unknown amount of time to stabilise, and where * repeated checks are the only way to detect that a stable state has been reached. * * @param consistentTime how long the result should be consistent for before it is returned * @param totalTimeout how long in total to wait for stabilisation to occur * @param timeUnit time unit for time intervals * @param lambda an UnreliableSupplier which should be called * @param <T> the return type of the UnreliableSupplier * @return the result of the supplier if it returned a consistent result for the specified interval */ public static <T> T retryUntilConsistent(final int consistentTime, final int totalTimeout, @NotNull final TimeUnit timeUnit, @NotNull final Callable<T> lambda) { check("consistent time must be greater than 0", consistentTime > 0); check("total timeout must be greater than 0", totalTimeout > 0); long start = System.currentTimeMillis(); Object[] recentValue = {null}; long[] firstRecentValueTime = {0}; long[] bestRun = {0}; Object[] bestRunValue = {null}; long consistentTimeInMillis = TimeUnit.MILLISECONDS.convert(consistentTime, timeUnit); return Unreliables.retryUntilSuccess(totalTimeout, timeUnit, () -> { T value = lambda.call(); boolean valueIsSame = value == recentValue[0] || (value != null && value.equals(recentValue[0])); if (valueIsSame) { long now = System.currentTimeMillis(); long timeSinceFirstValue = now - firstRecentValueTime[0]; if (timeSinceFirstValue > bestRun[0]) { bestRun[0] = timeSinceFirstValue; bestRunValue[0] = value; } if (timeSinceFirstValue > consistentTimeInMillis) { return value; } } else { // Reset everything and see if the next call yields the same result as this time recentValue[0] = value; firstRecentValueTime[0] = System.currentTimeMillis(); } long timeSinceStart = System.currentTimeMillis() - start; if (bestRun[0] > 0) { throw new InconsistentResultsException(timeSinceStart, bestRunValue[0], bestRun[0]); } else { throw new ResultsNeverConsistentException(timeSinceStart); } }); } }