package tc.oc.commons.core.concurrent;
import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import tc.oc.commons.core.util.Comparables;
public final class ExecutorUtils {
private ExecutorUtils() {}
public static ListeningExecutorService asService(Executor executor) {
if(executor instanceof ListeningExecutorService) {
return (ListeningExecutorService) executor;
} else if(executor instanceof ExecutorService) {
return MoreExecutors.listeningDecorator((ExecutorService) executor);
} else {
return new ExecutorServiceDecorator(executor);
}
}
/**
* Await termination of the given {@link ExecutorService}. If it does not terminate within the
* given time, log a severe error to the given logger, mentioning that the given technique was
* used to try and shut it down.
*
* @return true if the executor terminated in time, false if it timed out
*/
public static boolean awaitTermination(ExecutorService service, Logger logger, Duration timeout, String technique) {
try {
service.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS);
return true;
} catch(InterruptedException e) {
logger.severe(technique + " shutdown of " + service + " did not complete within " + timeout);
return false;
}
}
/**
* Shutdown the given executor using {@link ExecutorService#shutdownNow}, and log a severe
* error if fails to terminate within the given time.
*
* @return true if the executor terminated in time, false if it timed out
*/
public static boolean shutdownImpatiently(ExecutorService service, Logger logger, Duration timeout) {
service.shutdownNow();
return awaitTermination(service, logger, timeout, "Impatient");
}
/**
* Shutdown the given executor using {@link ExecutorService#shutdown} and await termination.
* If it fails to terminate before {@code patientTimeout}, log a severe error and then call
* {@link #shutdownImpatiently} with {@code impatientTimeout}.
*
* This approach should only be used if the executor's tasks are expected to always terminate
* on their own, or if the executor is expected to not have any running tasks. If tasks might
* need to be interrupted, then just skip this step and call {@link #shutdownImpatiently}.
*
* Particularly, if tasks might be trying to sync with the thread that is doing the shutdown,
* then they should always be interrupted in order to avoid deadlock.
*
* @return true if the executor terminated in time for either of the two attempts,
* false if it timed out for both
*/
public static boolean shutdownPatiently(ExecutorService service, Logger logger, Duration patientTimeout, Duration impatientTimeout) {
if(Comparables.greaterThan(patientTimeout, Duration.ZERO)) {
service.shutdown();
if(awaitTermination(service, logger, patientTimeout, "Patient")) return true;
}
return shutdownImpatiently(service, logger, impatientTimeout);
}
}