blob: a0d3d37ec867139408ad36a11b69cc458561ae8d [file] [log] [blame]
package org.mockitousage.verification;
import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.MAX_PRIORITY;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.locks.LockSupport.parkUntil;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
class DelayedExecution {
private static final int CORE_POOL_SIZE = 3;
/**
* Defines the number of milliseconds we expecting a Thread might need to unpark, we use this to avoid "oversleeping" while awaiting the deadline for
*/
private static final long MAX_EXPECTED_OVERSLEEP_MILLIS = 50;
private final ScheduledExecutorService executor;
public DelayedExecution() {
this.executor = newScheduledThreadPool(CORE_POOL_SIZE, maxPrioThreadFactory());
}
public void callAsync(long delay, TimeUnit timeUnit, Runnable r) {
long deadline = timeUnit.toMillis(delay) + currentTimeMillis();
executor.submit(delayedExecution(r, deadline));
}
public void close() throws InterruptedException {
executor.shutdownNow();
if (!executor.awaitTermination(5, SECONDS)) {
throw new IllegalStateException("This delayed excution did not terminated after 5 seconds");
}
}
private static Runnable delayedExecution(final Runnable r, final long deadline) {
return new Runnable() {
@Override
public void run() {
//we park the current Thread till 50ms before we want to execute the runnable
parkUntil(deadline - MAX_EXPECTED_OVERSLEEP_MILLIS);
//now we closing to the deadline by burning CPU-time in a loop
burnRemaining(deadline);
System.out.println("[DelayedExecution] exec delay = "+(currentTimeMillis() - deadline)+"ms");
r.run();
}
/**
* Loop in tight cycles until we reach the dead line. We do this cause sleep or park is very not precise,
* this can causes a Thread to under- or oversleep, sometimes by +50ms.
*/
private void burnRemaining(final long deadline) {
long remaining;
do {
remaining = deadline - currentTimeMillis();
} while (remaining > 0);
}
};
}
private static ThreadFactory maxPrioThreadFactory() {
return new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true); // allows the JVM to exit when clients forget to call DelayedExecution.close()
t.setPriority(MAX_PRIORITY);
return t;
}
};
}
}