
org.kiwiproject.beta.concurrent.KiwiFutures Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kiwi-beta Show documentation
Show all versions of kiwi-beta Show documentation
Experimental code that might eventually move into kiwi, or just to try something out...
package org.kiwiproject.beta.concurrent;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import lombok.experimental.UtilityClass;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;
/**
* Utilities related to {@link Future}.
*/
@UtilityClass
@Beta
public class KiwiFutures {
/**
* This is the default handler for InterruptedException. It ensures that the current thread is re-interrupted by
* calling {@link Thread#interrupt()} on the current thread, and then throws IllegalStateException with the
* InterruptedException as the cause. This satisfies Sonar rule java:S2142 (InterruptedException should not be ignored).
* In this class, we've suppressed Sonar's java:S2142 rule because we are interrupting the thread by default (just
* not in test code).
*/
private static final Function DEFAULT_INTERRUPTED_EXCEPTION_HANDLER = e -> {
Thread.currentThread().interrupt();
return new IllegalStateException("Task was interrupted", e);
};
/**
* This is the handler for InterruptedException. This exists to facilitate testing the case when a Future throws
* an InterruptedException, by allowing tests to replace it, so that test threads are not interrupted.
*/
@VisibleForTesting
static Function interruptedExceptionHandler = DEFAULT_INTERRUPTED_EXCEPTION_HANDLER;
/**
* This can be used in test code to reset the InterruptedException handler. The easiest thing to do is call this
* method after each test.
*/
@VisibleForTesting
static void resetInterruptedExceptionHandler() {
KiwiFutures.interruptedExceptionHandler = DEFAULT_INTERRUPTED_EXCEPTION_HANDLER;
}
/**
* Represents the state of a {@link Future}.
*
* @implNote This is a copy of the Future.State enum that was added in JDK 19.
*/
public enum FutureState {
/**
* The task has not completed.
*/
RUNNING,
/**
* The task completed with a result.
*
* @see KiwiFutures#resultNow(Future)
*/
SUCCESS,
/**
* The task completed with an exception.
*
* @see KiwiFutures#exceptionNow(Future)
*/
FAILED,
/**
* The task was canceled.
*
* @see Future#cancel(boolean)
*/
CANCELLED
}
/**
* Returns the computed result, without waiting.
*
* @param the result type of the future
* @param future the Future that should have completed with a result
* @return the result of the task
* @throws IllegalArgumentException if the future is null
* @throws IllegalStateException if the task has not completed, was interrupted, or did not complete with a result
* @apiNote JDK 19 adds {@code resultNow} as an instance method in {@link Future}, at which point this method
* will no longer be needed. Code written for JDKs before 19 can use this method as a stand-in.
* @implNote This implementation is mostly based on the JDK 19 code from {@code Future#resultNow}. The main
* difference is that this implementation removes the {@code while} loop and handles the InterruptedException by
* catching it, re-interrupting the current thread, and throwing an IllegalStateException. These changes were
* made because it seems that the JDK 19 implementation has an infinite loop, since it uses a "while (true)" loop
* which, when it catches InterruptedException, sets a boolean flag variable, but then allows execution to proceed.
* The code will then return to the top of the loop, and unless I am missing something, will encounter another
* InterruptedException when it calls {@code get} on the Future, and repeat forever (or until the program is
* actually shut down). I suspect it is likely I am missing something as to why that loop exists in the JDK 19
* implementation, but since I cannot explain it, that logic was removed from this implementation. See
* Future#resultNow
* for the JDK 19 implementation. If anyone happens upon this code and knows the reason why JDK 19 contains the
* loop, please create an issue or discussion in kiwi-beta.
*/
@SuppressWarnings("java:S2142")
public static V resultNow(Future future) {
checkArgumentNotNull(future);
if (isNotDone(future)) {
throw new IllegalStateException("Task has not completed");
}
try {
return future.get();
} catch (InterruptedException e) {
throw interruptedExceptionHandler.apply(e);
} catch (ExecutionException e) {
throw new IllegalStateException("Task completed with exception", e.getCause());
} catch (CancellationException e) {
throw new IllegalStateException("Task was cancelled", e);
}
}
/**
* Returns the exception thrown by the task, without waiting.
*
* @param the result type of the future
* @param future the Future that should have completed with an Exception
* @return the exception thrown by the task
* @throws IllegalArgumentException if the future is null
* @throws IllegalStateException if the task has not completed, was interrupted, completed normally, or was canceled
* @apiNote JDK 19 adds {@code exceptionNow} as an instance method in {@link Future}, at which point this method
* will no longer be needed. Code written for JDKs before 19 can use this method as a stand-in.
* @implNote See the implementation note in {@link #resultNow(Future)}. The same loop-based implementation exists
* in the JDK 19 {@code Future#exceptionNow}, so the same reasoning applies to this method (in terms of why this
* method does not contain the same loop logic). See
* Future#exceptionNow
* for the JDK 19 implementation.
*/
@SuppressWarnings("java:S2142")
public static Throwable exceptionNow(Future future) {
checkArgumentNotNull(future);
if (isNotDone(future)) {
throw new IllegalStateException("Task has not completed");
}
if (future.isCancelled()) {
throw new IllegalStateException("Task was cancelled");
}
try {
future.get();
throw new IllegalStateException("Task completed with a result");
} catch (InterruptedException e) {
throw interruptedExceptionHandler.apply(e);
} catch (ExecutionException e) {
return e.getCause();
}
}
/**
* Returns the state of a {@link Future}.
*
* @param the result type of the future
* @param future the Future to check
* @return the {@link FutureState} representing the Future's state
* @throws IllegalArgumentException if the future is null
* @throws IllegalStateException if the task was interrupted
* @apiNote JDK 19 adds {@code state} as an instance method in {@link Future}, at which point this method
* will no longer be needed. Code written for JDKs before 19 can use this method as a stand-in.
* @implNote See the implementation note in {@link #resultNow(Future)}. The same loop-based implementation exists
* in the JDK 19 {@code Future#state}, so the same reasoning applies to this method (in terms of why this
* method does not contain the same loop logic). See
* Future#state
* for the JDK 19 implementation.
*/
@SuppressWarnings("java:S2142")
public static FutureState state(Future future) {
checkArgumentNotNull(future);
if (isNotDone(future)) {
return FutureState.RUNNING;
}
if (future.isCancelled()) {
return FutureState.CANCELLED;
}
try {
future.get();
return FutureState.SUCCESS;
} catch (InterruptedException e) {
throw interruptedExceptionHandler.apply(e);
} catch (ExecutionException e) {
return FutureState.FAILED;
}
}
/**
* Check whether a {@link Future} is done.
*
* This is a convenience method for those who prefer (like me) to read code like:
*
* var future = ...;
* if (isNotDone(future)) {
* // ...
* }
*
* instead of code like:
*
* if (!future.isDone()) {
* // ...
* }
*
* And since we can't add a {@code isNotDone()} method to Future, this is this best we can do in Java. And while
* Lombok has {@code ExtensionMethod}, it is experimental so best to avoid.
*
* @param the result type of the future
* @param future the Future to check
* @return true if the future is not done, and false if it is done (inverse of {@link Future#isDone})
*/
public static boolean isNotDone(Future future) {
return !future.isDone();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy