This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
The following commit(s) were added to refs/heads/master by this push: new b8e788a08 ThreadUtils.sleep(Duration) should handle the underlying OS time changing new c722fe80b Merge branch 'master' of https://gitbox.apache.org/repos/asf/commons-io.git b8e788a08 is described below commit b8e788a0875789303a957b077bef86e98cb83af3 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Tue Apr 8 12:21:40 2025 -0400 ThreadUtils.sleep(Duration) should handle the underlying OS time changing --- src/changes/changes.xml | 1 + .../java/org/apache/commons/io/ThreadUtils.java | 28 ++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1684f86a7..fdfdf0a0c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -74,6 +74,7 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="fix" due-to="Gary Gregory">General Javadoc improvements.</action> <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> <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> + <action dev="ggregory" type="fix" due-to="zhouchongwen, Gary Gregory">ThreadUtils.sleep(Duration) should handle the underlying OS time changing.</action> <!-- ADD --> <action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action> diff --git a/src/main/java/org/apache/commons/io/ThreadUtils.java b/src/main/java/org/apache/commons/io/ThreadUtils.java index f6b7713ca..e41af9a84 100644 --- a/src/main/java/org/apache/commons/io/ThreadUtils.java +++ b/src/main/java/org/apache/commons/io/ThreadUtils.java @@ -38,17 +38,31 @@ private static int getNanosOfMilli(final Duration duration) { * </p> * * @param duration the sleep duration. - * @throws InterruptedException if interrupted + * @throws InterruptedException if interrupted. * @see Thread#sleep(long, int) */ public static void sleep(final Duration duration) throws InterruptedException { // Using this method avoids depending on the vagaries of the precision and accuracy of system timers and schedulers. - final Instant finishInstant = Instant.now().plus(duration); - Duration remainingDuration = duration; - do { - Thread.sleep(remainingDuration.toMillis(), getNanosOfMilli(remainingDuration)); - remainingDuration = Duration.between(Instant.now(), finishInstant); - } while (!remainingDuration.isNegative()); + try { + // Use the JVM elapsed time, avoids issues with DST changes and manual OS time changes. + final long nanoStart = System.nanoTime(); + final long finishNanos = nanoStart + duration.toNanos(); // toNanos(): Possible ArithmeticException, otherwise wrap around OK. + Duration remainingDuration = duration; + long nowNano; + do { + Thread.sleep(remainingDuration.toMillis(), getNanosOfMilli(remainingDuration)); + nowNano = System.nanoTime(); + remainingDuration = Duration.ofNanos(finishNanos - nowNano); + } while (nowNano - finishNanos < 0); // handles wrap around, see Thread#sleep(long, int). + } catch (final ArithmeticException e) { + // Use the current time + final Instant finishInstant = Instant.now().plus(duration); + Duration remainingDuration = duration; + do { + Thread.sleep(remainingDuration.toMillis(), getNanosOfMilli(remainingDuration)); + remainingDuration = Duration.between(Instant.now(), finishInstant); + } while (!remainingDuration.isNegative()); + } } /**