Skip to content

Commit b8e788a

Browse files
committed
ThreadUtils.sleep(Duration) should handle the underlying OS time
changing
1 parent 55da87d commit b8e788a

File tree

2 files changed

+22
-7
lines changed

2 files changed

+22
-7
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The <action> type attribute can be add,update,fix,remove.
7474
<action dev="ggregory" type="fix" due-to="Gary Gregory">General Javadoc improvements.</action>
7575
<action dev="ggregory" type="fix" due-to="Gary Gregory">Calling QueueInputStream.QueueInputStream(null) maps to the same kind of default blocking queue as QueueInputStream.Builder.setBlockingQueue(null).</action>
7676
<action dev="ggregory" type="fix" due-to="Gary Gregory">CopyDirectoryVisitor creates incorrect file names when copying between different file systems that use different file system separators ("/" versus "\"); fixes PathUtils.copyDirectory(Path, Path, CopyOption...).</action>
77+
<action dev="ggregory" type="fix" due-to="zhouchongwen, Gary Gregory">ThreadUtils.sleep(Duration) should handle the underlying OS time changing.</action>
7778
<!-- ADD -->
7879
<action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action>
7980
<action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action>

src/main/java/org/apache/commons/io/ThreadUtils.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,31 @@ private static int getNanosOfMilli(final Duration duration) {
3838
* </p>
3939
*
4040
* @param duration the sleep duration.
41-
* @throws InterruptedException if interrupted
41+
* @throws InterruptedException if interrupted.
4242
* @see Thread#sleep(long, int)
4343
*/
4444
public static void sleep(final Duration duration) throws InterruptedException {
4545
// Using this method avoids depending on the vagaries of the precision and accuracy of system timers and schedulers.
46-
final Instant finishInstant = Instant.now().plus(duration);
47-
Duration remainingDuration = duration;
48-
do {
49-
Thread.sleep(remainingDuration.toMillis(), getNanosOfMilli(remainingDuration));
50-
remainingDuration = Duration.between(Instant.now(), finishInstant);
51-
} while (!remainingDuration.isNegative());
46+
try {
47+
// Use the JVM elapsed time, avoids issues with DST changes and manual OS time changes.
48+
final long nanoStart = System.nanoTime();
49+
final long finishNanos = nanoStart + duration.toNanos(); // toNanos(): Possible ArithmeticException, otherwise wrap around OK.
50+
Duration remainingDuration = duration;
51+
long nowNano;
52+
do {
53+
Thread.sleep(remainingDuration.toMillis(), getNanosOfMilli(remainingDuration));
54+
nowNano = System.nanoTime();
55+
remainingDuration = Duration.ofNanos(finishNanos - nowNano);
56+
} while (nowNano - finishNanos < 0); // handles wrap around, see Thread#sleep(long, int).
57+
} catch (final ArithmeticException e) {
58+
// Use the current time
59+
final Instant finishInstant = Instant.now().plus(duration);
60+
Duration remainingDuration = duration;
61+
do {
62+
Thread.sleep(remainingDuration.toMillis(), getNanosOfMilli(remainingDuration));
63+
remainingDuration = Duration.between(Instant.now(), finishInstant);
64+
} while (!remainingDuration.isNegative());
65+
}
5266
}
5367

5468
/**

0 commit comments

Comments
 (0)