• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DelayedExecution()22     public DelayedExecution() {
23         this.executor = newScheduledThreadPool(CORE_POOL_SIZE, maxPrioThreadFactory());
24     }
25 
callAsync(long delay, TimeUnit timeUnit, Runnable r)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 
close()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 
delayedExecution(final Runnable r, final long deadline)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