1package org.mockitousage.verification; 2 3import static java.lang.System.currentTimeMillis; 4import static java.lang.Thread.MAX_PRIORITY; 5import static java.util.concurrent.Executors.newScheduledThreadPool; 6import static java.util.concurrent.TimeUnit.SECONDS; 7import static java.util.concurrent.locks.LockSupport.parkUntil; 8 9import java.util.concurrent.ScheduledExecutorService; 10import java.util.concurrent.ThreadFactory; 11import java.util.concurrent.TimeUnit; 12 13class DelayedExecution { 14 private static final int CORE_POOL_SIZE = 3; 15 /** 16 * Defines the number of milliseconds we expecting a Thread might need to unpark, we use this to avoid "oversleeping" while awaiting the deadline for 17 */ 18 private static final long MAX_EXPECTED_OVERSLEEP_MILLIS = 50; 19 20 private final ScheduledExecutorService executor; 21 22 public DelayedExecution() { 23 this.executor = newScheduledThreadPool(CORE_POOL_SIZE, maxPrioThreadFactory()); 24 } 25 26 public void callAsync(long delay, TimeUnit timeUnit, Runnable r) { 27 long deadline = timeUnit.toMillis(delay) + currentTimeMillis(); 28 29 executor.submit(delayedExecution(r, deadline)); 30 } 31 32 public void close() throws InterruptedException { 33 executor.shutdownNow(); 34 35 if (!executor.awaitTermination(5, SECONDS)) { 36 throw new IllegalStateException("This delayed excution did not terminated after 5 seconds"); 37 } 38 } 39 40 private static Runnable delayedExecution(final Runnable r, final long deadline) { 41 return new Runnable() { 42 @Override 43 public void run() { 44 //we park the current Thread till 50ms before we want to execute the runnable 45 parkUntil(deadline - MAX_EXPECTED_OVERSLEEP_MILLIS); 46 //now we closing to the deadline by burning CPU-time in a loop 47 burnRemaining(deadline); 48 49 System.out.println("[DelayedExecution] exec delay = "+(currentTimeMillis() - deadline)+"ms"); 50 51 r.run(); 52 } 53 54 /** 55 * Loop in tight cycles until we reach the dead line. We do this cause sleep or park is very not precise, 56 * this can causes a Thread to under- or oversleep, sometimes by +50ms. 57 */ 58 private void burnRemaining(final long deadline) { 59 long remaining; 60 do { 61 remaining = deadline - currentTimeMillis(); 62 } while (remaining > 0); 63 } 64 }; 65 } 66 67 private static ThreadFactory maxPrioThreadFactory() { 68 return new ThreadFactory() { 69 @Override 70 public Thread newThread(Runnable r) { 71 Thread t = new Thread(r); 72 t.setDaemon(true); // allows the JVM to exit when clients forget to call DelayedExecution.close() 73 t.setPriority(MAX_PRIORITY); 74 return t; 75 } 76 }; 77 } 78} 79