de.mklinger.qetcher.client.common.concurrent.Delay Maven / Gradle / Ivy
package de.mklinger.qetcher.client.common.concurrent;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
/**
* Code taken from OpenJDK 11, GPL + PUBLIC DOMAIN.
*/
public class Delay {
/** No instantiation. */
private Delay() {}
private static final boolean USE_COMMON_POOL =
(ForkJoinPool.getCommonPoolParallelism() > 1);
/**
* Default executor -- ForkJoinPool.commonPool() unless it cannot
* support parallelism.
*/
private static final Executor ASYNC_POOL = USE_COMMON_POOL ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
/** Fallback if ForkJoinPool.commonPool() cannot support parallelism */
private static final class ThreadPerTaskExecutor implements Executor {
@Override
public void execute(final Runnable r) { new Thread(r).start(); }
}
/**
* Returns a new Executor that submits a task to the default
* executor after the given delay (or no delay if non-positive).
* Each delay commences upon invocation of the returned executor's
* {@code execute} method.
*
* @param delay how long to delay, in units of {@code unit}
* @param unit a {@code TimeUnit} determining how to interpret the
* {@code delay} parameter
* @return the new delayed executor
* @since 9
*/
public static Executor delayedExecutor(final long delay, final TimeUnit unit) {
if (unit == null) {
throw new NullPointerException();
}
return new DelayedExecutor(delay, unit, ASYNC_POOL);
}
/**
* Singleton delay scheduler, used only for starting and
* cancelling tasks.
*/
private static final class Delayer {
static ScheduledFuture> delay(final Runnable command, final long delay,
final TimeUnit unit) {
return delayer.schedule(command, delay, unit);
}
static final class DaemonThreadFactory implements ThreadFactory {
@Override
public Thread newThread(final Runnable r) {
final Thread t = new Thread(r);
t.setDaemon(true);
t.setName("CompletableFutureDelayScheduler");
return t;
}
}
static final ScheduledThreadPoolExecutor delayer;
static {
(delayer = new ScheduledThreadPoolExecutor(
1, new DaemonThreadFactory())).
setRemoveOnCancelPolicy(true);
}
}
// Little class-ified lambdas to better support monitoring
private static final class DelayedExecutor implements Executor {
final long delay;
final TimeUnit unit;
final Executor executor;
DelayedExecutor(final long delay, final TimeUnit unit, final Executor executor) {
this.delay = delay; this.unit = unit; this.executor = executor;
}
@Override
public void execute(final Runnable r) {
Delayer.delay(new TaskSubmitter(executor, r), delay, unit);
}
}
/** Action to submit user task */
private static final class TaskSubmitter implements Runnable {
final Executor executor;
final Runnable action;
TaskSubmitter(final Executor executor, final Runnable action) {
this.executor = executor;
this.action = action;
}
@Override
public void run() { executor.execute(action); }
}
// -------------- timeout
/**
* Exceptionally completes the given CompletableFuture with
* a {@link TimeoutException} if not otherwise completed
* before the given timeout.
*
* @param timeout how long to wait before completing exceptionally
* with a TimeoutException, in units of {@code unit}
* @param unit a {@code TimeUnit} determining how to interpret the
* {@code timeout} parameter
*/
public static CompletableFuture timeout(final CompletableFuture cf, final long timeout, final TimeUnit unit, final Supplier s) {
if (unit == null) {
throw new NullPointerException();
}
if (!cf.isDone()) {
cf.whenComplete(new Canceller(Delayer.delay(new Timeout(cf, s),
timeout, unit)));
}
return cf;
}
/** Action to cancel unneeded timeouts */
private static final class Canceller implements BiConsumer