/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.testsupport.concurrency;
import java.util.concurrent.TimeUnit;
import junit.framework.AssertionFailedError;
/**
* A utility allowing to re-execute a piece of code in the current thread until it "works".
* <p>
* Meant to be used with lambdas.
*
* @see #pollAssertion(ThrowingRunnable)
*
* @author Yoann Rodiere
*/
public final class Poller {
public static Poller milliseconds(long timeoutMs, long stepMs) {
return create( timeoutMs, stepMs, TimeUnit.MILLISECONDS );
}
public static Poller nanoseconds(long timeoutNanos, long stepNanos) {
return create( timeoutNanos, stepNanos, TimeUnit.NANOSECONDS );
}
private static Poller create(long timeoutValue, long stepValue, TimeUnit timeUnit) {
return new Poller( timeUnit.toNanos( timeoutValue ), timeUnit.toNanos( stepValue ) );
}
private final long timeoutValueNanos;
private final long stepValueNanos;
private Poller(long timeoutValueNanos, long stepValueNanos) {
this.timeoutValueNanos = timeoutValueNanos;
this.stepValueNanos = stepValueNanos;
}
/**
* Run the given statement repeatedly until it doesn't throw any {@link AssertionError},
* or throw the {@link AssertionError} if the timeout is reached.
*/
public <E extends Exception> void pollAssertion(ThrowingRunnable<E> runnable) throws E {
long initialNanoTime = System.nanoTime();
long deadline = initialNanoTime + timeoutValueNanos;
int nbOfFailedAttempts = 0;
AssertionError assertionError;
do {
sleepStep();
try {
runnable.run();
return; // Don't wait any more: the assertion passed, we're good to go.
}
catch (AssertionError e) {
/*
* Only keep the very last assertion error, because it's probably the most relevant:
* the runnable may perform multiple assertions, and for instance the first assertion
* might fail for 2s, then be okay, the second assertion may become okay after 3s, etc.
* In the end what we want to know is which assertions was failing when we timed out.
*/
assertionError = e;
++nbOfFailedAttempts;
}
} while ( System.nanoTime() < deadline );
long timeSpentNanos = System.nanoTime() - initialNanoTime;
throw newAssertionPollingError( assertionError, nbOfFailedAttempts, timeSpentNanos );
}
private void sleepStep() {
try {
TimeUnit.NANOSECONDS.sleep( stepValueNanos );
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException( "Interrupted while waiting for an assertion to pass.", e );
}
}
public interface ThrowingRunnable<E extends Exception> {
void run() throws E;
}
private AssertionError newAssertionPollingError(AssertionError lastAssertionError, int nbOfFailedAttempts, long timeSpentNanos) {
AssertionError error = new AssertionFailedError(
"Assertion failed even after " + nbOfFailedAttempts + " attempts in " + timeSpentNanos + "ns : "
+ lastAssertionError.getMessage()
);
error.initCause( lastAssertionError );
return error;
}
}