Add and use ThreadUtils
Inspired by DaGeRe's PR Avoid Code Duplication: Reuse Sleep from
ThreadMonitor
MODIFY
src/changes/changes.xml
src/changes/changes.xml
@@ -454,6 +454,9 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="IO-784" dev="ggregory" type="add" due-to="Fredrik Kjellberg, Gary Gregory">
Add support for Appendable to HexDump #418.
</action>
+ <action dev="ggregory" type="add" due-to="DaGeRe, Gary Gregory">
+ Add and use ThreadUtils.
+ </action>
<!-- UPDATE -->
<action dev="kinow" type="update" due-to="Dependabot, Gary Gregory">
Bump actions/cache from 2.1.6 to 3.0.10 #307, #337, #393.

MODIFY
src/main/java/org/apache/commons/io/ThreadMonitor.java
src/main/java/org/apache/commons/io/ThreadMonitor.java
package org.apache.commons.io;
import java.time.Duration;
-import java.time.Instant;
/**
* Monitors a thread, interrupting it if it reaches the specified timeout.
*/
class ThreadMonitor implements Runnable {
- private static int getNanosOfMilli(final Duration duration) {
- return duration.getNano() % 1_000_000;
- }
- /**
- * Sleeps for a guaranteed minimum duration unless interrupted.
- *
- * This method exists because Thread.sleep(100) can sleep for 0, 70, 100 or 200ms or anything else it deems appropriate.
- * Read {@link Thread#sleep(long, int)}} for further interesting details.
- *
- * @param duration the sleep duration.
- * @throws InterruptedException if interrupted.
- */
- static void sleep(final Duration duration) throws InterruptedException {
- 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());
- }
-
/**
* Starts monitoring the current thread.
*
@@ -123,7 +101,7 @@ private ThreadMonitor(final Thread thread, final Duration timeout) {
@Override
public void run() {
try {
- sleep(timeout);
+ ThreadUtils.sleep(timeout);
thread.interrupt();
} catch (final InterruptedException e) {
// timeout not reached

ADD
src/main/java/org/apache/commons/io/ThreadUtils.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io;
+
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * Helps work with threads.
+ *
+ * @since 2.12.0
+ */
+public class ThreadUtils {
+
+ static int getNanosOfMilli(final Duration duration) {
+ return duration.getNano() % 1_000_000;
+ }
+
+ /**
+ * Sleeps for a guaranteed minimum duration unless interrupted.
+ *
+ * This method exists because Thread.sleep(100) can sleep for 0, 70, 100 or 200ms or anything else it deems appropriate.
+ * Read {@link Thread#sleep(long, int)}} for further interesting details.
+ *
+ * TODO The above needs confirmation now that we've been on Java 8 for a while.
+ *
+ * @param duration the sleep duration.
+ * @throws InterruptedException if interrupted.
+ */
+ public static void sleep(final Duration duration) throws InterruptedException {
+ 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());
+ }
+
+}

MODIFY
src/main/java/org/apache/commons/io/file/PathUtils.java
src/main/java/org/apache/commons/io/file/PathUtils.java
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.ThreadUtils;
import org.apache.commons.io.file.Counters.PathCounters;
import org.apache.commons.io.file.attribute.FileTimes;
import org.apache.commons.io.filefilter.IOFileFilter;
@@ -1691,7 +1692,7 @@ public static boolean waitFor(final Path file, final Duration timeout, final Lin
return false;
}
try {
- Thread.sleep(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli()));
+ ThreadUtils.sleep(Duration.ofMillis(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli())));
} catch (final InterruptedException ignore) {
interrupted = true;
} catch (final Exception ex) {

MODIFY
src/main/java/org/apache/commons/io/input/Tailer.java
src/main/java/org/apache/commons/io/input/Tailer.java
import java.util.Objects;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.ThreadUtils;
import org.apache.commons.io.file.PathUtils;
import org.apache.commons.io.file.attribute.FileTimes;
@@ -899,7 +900,7 @@ public void run() {
listener.fileNotFound();
}
if (reader == null) {
- Thread.sleep(delayDuration.toMillis());
+ ThreadUtils.sleep(delayDuration);
} else {
// The current position in the file
position = tailAtEnd ? tailable.size() : 0;
@@ -929,7 +930,7 @@ public void run() {
} catch (final FileNotFoundException e) {
// in this case we continue to use the previous reader and position values
listener.fileNotFound();
- Thread.sleep(delayDuration.toMillis());
+ ThreadUtils.sleep(delayDuration);
}
continue;
}
@@ -954,7 +955,7 @@ public void run() {
if (reOpen && reader != null) {
reader.close();
}
- Thread.sleep(delayDuration.toMillis());
+ ThreadUtils.sleep(delayDuration);
if (getRun() && reOpen) {
reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
reader.seek(position);

MODIFY
src/main/java/org/apache/commons/io/monitor/FileAlterationMonitor.java
src/main/java/org/apache/commons/io/monitor/FileAlterationMonitor.java
*/
package org.apache.commons.io.monitor;
+import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Stream;
+import org.apache.commons.io.ThreadUtils;
+
/**
* A runnable that spawns a monitoring thread triggering any
* registered {@link FileAlterationObserver} at a specified interval.
@@ -143,7 +146,7 @@ public void run() {
break;
}
try {
- Thread.sleep(intervalMillis);
+ ThreadUtils.sleep(Duration.ofMillis(intervalMillis));
} catch (final InterruptedException ignored) {
// ignore
}

MODIFY
src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java
src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
+import org.apache.commons.io.ThreadUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.EmptyFileFilter;
@@ -185,7 +187,7 @@ public void testFolderWhileDeletingAsync(final Supplier<AccumulatorPathVisitor>
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attributes) throws IOException {
// Slow down the walking a bit to try and cause conflicts with the deletion thread
try {
- Thread.sleep(10);
+ ThreadUtils.sleep(Duration.ofMillis(10));
} catch (final InterruptedException ignore) {
// e.printStackTrace();
}
@@ -209,7 +211,7 @@ public FileVisitResult visitFile(final Path path, final BasicFileAttributes attr
Files.walkFileTree(tempDirPath, countingFileFilter);
} finally {
if (!deleted.get()) {
- Thread.sleep(1000);
+ ThreadUtils.sleep(Duration.ofMillis(1000));
}
if (!deleted.get()) {
executor.awaitTermination(5, TimeUnit.SECONDS);

MODIFY
src/test/java/org/apache/commons/io/input/TimestampedObserverTest.java
src/test/java/org/apache/commons/io/input/TimestampedObserverTest.java
package org.apache.commons.io.input;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.assertFalse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.time.Duration;
import java.time.Instant;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.ThreadUtils;
import org.junit.jupiter.api.Test;
/**
public void test() throws IOException, InterruptedException {
final Instant before = Instant.now();
// Some OS' clock granularity may be high.
- Thread.sleep(20);
+ ThreadUtils.sleep(Duration.ofMillis(20));
final TimestampedObserver timestampedObserver = new TimestampedObserver();
assertFalse(timestampedObserver.isClosed());
// Java 8 instant resolution is not great.
- Thread.sleep(20);
+ ThreadUtils.sleep(Duration.ofMillis(20));
// toString() should not blow up before close().
assertNotNull(timestampedObserver.toString());
assertTrue(timestampedObserver.getOpenInstant().isAfter(before));

MODIFY
src/test/java/org/apache/commons/io/monitor/FileAlterationMonitorTest.java
src/test/java/org/apache/commons/io/monitor/FileAlterationMonitorTest.java
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
+import org.apache.commons.io.ThreadUtils;
import org.apache.commons.io.test.TestUtils;
import org.junit.jupiter.api.Test;
@@ -192,7 +194,7 @@ public Thread newThread(final Runnable r) {
monitor.start();
assertFalse(createdThreads.isEmpty());
- Thread.sleep(10); // wait until the watcher thread enters Thread.sleep()
+ ThreadUtils.sleep(Duration.ofMillis(10)); // wait until the watcher thread enters Thread.sleep()
monitor.stop(100);
createdThreads.forEach(thread -> assertFalse(thread.isAlive(), "The FileAlterationMonitor did not stop the threads it created."));

Add and use ThreadUtils

Inspired by DaGeRe's PR Avoid Code Duplication: Reuse Sleep from
ThreadMonitor #66