diff --git a/src/java.base/share/classes/java/lang/Process.java b/src/java.base/share/classes/java/lang/Process.java index 756705285d966..3da1809f4ac20 100644 --- a/src/java.base/share/classes/java/lang/Process.java +++ b/src/java.base/share/classes/java/lang/Process.java @@ -32,6 +32,7 @@ import java.lang.ProcessBuilder.Redirect; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; +import java.time.Duration; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ForkJoinPool; @@ -456,14 +457,54 @@ public final BufferedWriter outputWriter(Charset charset) { * @since 1.8 */ public boolean waitFor(long timeout, TimeUnit unit) - throws InterruptedException - { - long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions + throws InterruptedException { + Objects.requireNonNull(unit, "unit"); // throw NPE before other conditions + if (hasExited()) return true; if (timeout <= 0) return false; + return waitForNanos(unit.toNanos(timeout)); + } + + /** + * Causes the current thread to wait, if necessary, until the + * process represented by this {@code Process} object has + * terminated, or the specified waiting duration elapses. + * + *

If the process has already terminated then this method returns + * immediately with the value {@code true}. If the process has not + * terminated and the duration is not positive, then + * this method returns immediately with the value {@code false}. + * + *

The default implementation of this method polls the {@code exitValue} + * to check if the process has terminated. Concrete implementations of this + * class are strongly encouraged to override this method with a more + * efficient implementation. + * + * @param duration the maximum duration to wait; if not positive, + * this method returns immediately. + * @return {@code true} if the process has exited and {@code false} if + * the waiting duration elapsed before the process has exited. + * @throws InterruptedException if the current thread is interrupted + * while waiting. + * @throws NullPointerException if duration is null + * @since 24 + */ + public boolean waitFor(Duration duration) + throws InterruptedException { + Objects.requireNonNull(duration, "duration"); // throw NPE before other conditions + + if (hasExited()) + return true; + if (duration.isZero() || duration.isNegative()) + return false; + + return waitForNanos(TimeUnit.NANOSECONDS.convert(duration)); + } + + private boolean waitForNanos(long remainingNanos) throws InterruptedException { long deadline = System.nanoTime() + remainingNanos; do { Thread.sleep(Math.min(TimeUnit.NANOSECONDS.toMillis(remainingNanos) + 1, 100)); diff --git a/test/jdk/java/lang/Process/WaitForDuration.java b/test/jdk/java/lang/Process/WaitForDuration.java new file mode 100644 index 0000000000000..353ab8cc2d591 --- /dev/null +++ b/test/jdk/java/lang/Process/WaitForDuration.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8336479 + * @summary Tests for Process.waitFor(Duration) + * @run junit WaitForDuration + */ + +import java.io.IOException; +import java.time.Duration; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +public class WaitForDuration { + static Stream durations() { + return Stream.of( + Arguments.of(Duration.ZERO, false), + Arguments.of(Duration.ofSeconds(-100), false), + Arguments.of(Duration.ofSeconds(100), true), + Arguments.of(Duration.ofSeconds(Long.MAX_VALUE), true), // nano overflow + Arguments.of(Duration.ofSeconds(Long.MIN_VALUE), false) // nano underflow + ); + } + + @ParameterizedTest + @MethodSource("durations") + void testEdgeDurations(Duration d, boolean expected) throws IOException, InterruptedException { + assertEquals(expected, new ProcessBuilder("sleep", "3").start().waitFor(d)); + } + + @Test + void testNullDuration() throws IOException, InterruptedException { + assertThrows(NullPointerException.class, () -> + new ProcessBuilder("sleep", "3").start().waitFor(null)); + } +}