![JAR search and dependency download from the Maven repository](/logo.png)
net.openhft.chronicle.testframework.Waiters Maven / Gradle / Ivy
package net.openhft.chronicle.testframework;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static net.openhft.chronicle.testframework.ThreadUtil.pause;
/**
* A utility class providing methods to create wait conditions. These conditions pause the execution of the
* program until certain criteria are met or until a specified amount of time has elapsed.
*
* The class provides functionality for boolean as well as generic conditions and uses a builder pattern
* for customization of the waiting behavior, such as the maximum time to wait and the interval to check
* the condition.
*/
public class Waiters {
// Default maximum time to wait for a condition in milliseconds
private static final int DEFAULT_MAX_WAIT_TIME_MS = 5_000;
// Default time interval to check the condition in milliseconds
private static final int DEFAULT_CHECK_INTERVAL_MS = 10;
// Default identity predicate for boolean conditions
private static final Predicate IDENTITY = t -> t;
// Default failure message for boolean conditions
private static final String DEFAULT_BOOLEAN_MESSAGE = "Condition not met";
// Default failure message for generic conditions, with placeholder for the last value
private static final String DEFAULT_GENERIC_MESSAGE = "Condition not met, lastValue = %s";
/**
* Waits for the specified condition to be true, throwing a {@link ConditionNotMetException} if the condition is not met within the specified time.
*
* This method is useful for synchronizing activities in concurrent programming, such as waiting for a connection to close.
*
* @param message The message to include in the exception if the condition is not met.
* @param condition A supplier of the condition being tested, which returns true when the condition is met.
* @param maxTimeToWaitMs The maximum amount of time to wait for the condition, in milliseconds.
* @throws ConditionNotMetException If the condition is not met within the specified time.
*/
public static void waitForCondition(String message, Supplier condition, long maxTimeToWaitMs) {
builder(condition)
.message(message)
.maxTimeToWaitMs(maxTimeToWaitMs)
.run();
}
/**
* Creates a builder for waiting on a boolean condition.
*
* This method simplifies the creation of waiters for boolean conditions.
*
* @param condition the condition, when it returns true the waiter will complete.
* @return the {@link WaiterBuilder} for further configuration.
*/
public static WaiterBuilder builder(Supplier condition) {
return builder(condition, IDENTITY).message(DEFAULT_BOOLEAN_MESSAGE);
}
/**
* Creates a builder for waiting on a generic condition, allowing customization of the condition testing.
*
* This method provides more flexibility for defining complex waiting conditions.
*
* @param valueSupplier The supplier of the value being tested, which will be regularly evaluated.
* @param conditionTester A predicate that determines if the condition has been met based on the supplied value.
* @param The type of the value being tested.
* @return the {@link WaiterBuilder} for further configuration.
*/
public static WaiterBuilder builder(Supplier valueSupplier, Predicate conditionTester) {
return new WaiterBuilder<>(valueSupplier, conditionTester);
}
public static class WaiterBuilder implements Runnable {
// Supplier that provides a value
private final Supplier valueSupplier;
// Predicate to test the provided value
private final Predicate conditionTester;
// Maximum time to wait for a condition in milliseconds
private long maxTimeToWaitMs = DEFAULT_MAX_WAIT_TIME_MS;
// Time interval to check the condition in milliseconds
private long checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS;
// Function to generate a message in case the condition isn't met
private Function messageGenerator = lastValue -> String.format(DEFAULT_GENERIC_MESSAGE, lastValue);
// Constructor
private WaiterBuilder(Supplier valueSupplier, Predicate conditionTester) {
this.valueSupplier = valueSupplier;
this.conditionTester = conditionTester;
}
/**
* Wait for the condition to be true, throw an {@link ConditionNotMetException}
* if the condition is not met in time.
*
* This method is overridden from the Runnable interface, allowing the WaiterBuilder
* to be run as a separate thread.
*/
@Override
public void run() {
long endTime = System.currentTimeMillis() + maxTimeToWaitMs;
while (true) {
final T value = valueSupplier.get();
if (conditionTester.test(value)) {
break; // Condition met, break the loop
}
if (System.currentTimeMillis() > endTime) {
// Condition wasn't met in the allowed time, throw an exception
throw new ConditionNotMetException(value, messageGenerator.apply(value));
}
// Condition wasn't met yet, pause before checking again
pause(checkIntervalMs);
}
}
/**
* Specifies a static message for the exception in case the condition isn't met.
*
* @param message the message to be displayed when an exception is thrown.
* @return the WaiterBuilder instance to allow for method chaining.
*/
public WaiterBuilder message(String message) {
// Override the default message generator with a function that returns the provided message
this.messageGenerator = lastValue -> message;
return this; // Return the instance for method chaining
}
/**
* Supply a function for generating the exception message in case the condition isn't met.
*
* @param messageGenerator a function that takes the last value as a parameter to create a message.
* @return this WaiterBuilder instance to allow method chaining.
*/
public WaiterBuilder messageGenerator(Function messageGenerator) {
this.messageGenerator = messageGenerator;
return this;
}
/**
* Specify the maximum amount of time to wait for the condition to be met.
*
* @param maxTimeToWaitMs the maximum time in milliseconds to wait for the condition.
* @return this WaiterBuilder instance to allow method chaining.
*/
public WaiterBuilder maxTimeToWaitMs(long maxTimeToWaitMs) {
this.maxTimeToWaitMs = maxTimeToWaitMs;
return this;
}
/**
* Specify the interval between checks of the condition. This determines how often the condition is evaluated.
*
* @param checkIntervalMs the interval between checks in milliseconds.
* @return this WaiterBuilder instance to allow method chaining.
*/
public WaiterBuilder checkIntervalMs(long checkIntervalMs) {
this.checkIntervalMs = checkIntervalMs;
return this;
}
}
/**
* An exception class that is thrown when a condition is not met within the specified time.
*/
public static class ConditionNotMetException extends RuntimeException {
private static final long serialVersionUID = 2827672436814649510L;
@SuppressWarnings("serial")
private final Object lastValue;
/**
* Constructor for the exception.
*
* @param lastValue the last value that was evaluated when the exception was thrown
* @param message the message to be included with the exception
*/
ConditionNotMetException(Object lastValue, String message) {
super(message);
this.lastValue = lastValue;
}
/**
* Retrieve the last value that was evaluated when the exception was thrown.
*
* @return the last value
*/
public Object lastValue() {
return lastValue;
}
}
}