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