• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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