Home | History | Annotate | Download | only in verification
      1 package org.mockitousage.verification;
      2 
      3 import static java.lang.System.currentTimeMillis;
      4 import static java.lang.Thread.MAX_PRIORITY;
      5 import static java.util.concurrent.Executors.newScheduledThreadPool;
      6 import static java.util.concurrent.TimeUnit.SECONDS;
      7 import static java.util.concurrent.locks.LockSupport.parkUntil;
      8 
      9 import java.util.concurrent.ScheduledExecutorService;
     10 import java.util.concurrent.ThreadFactory;
     11 import java.util.concurrent.TimeUnit;
     12 
     13 class 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