From c1c9704268e9e651cd82c8550913d8ac60aa494a Mon Sep 17 00:00:00 2001 From: Naoto Sato <naoto@openjdk.org> Date: Mon, 22 Jul 2024 17:17:46 +0000 Subject: [PATCH] 8336479: Provide Process.waitFor(Duration) Reviewed-by: liach, jpai, rriggs --- .../share/classes/java/lang/Process.java | 34 ++++++++- .../java/lang/Process/WaitForDuration.java | 73 +++++++++++++++++++ .../jdk/test/lib/process/ProcessTools.java | 10 +++ 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 test/jdk/java/lang/Process/WaitForDuration.java diff --git a/src/java.base/share/classes/java/lang/Process.java b/src/java.base/share/classes/java/lang/Process.java index 756705285d9..a01a1f93fd2 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.io.*; 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; @@ -475,6 +476,35 @@ public abstract class Process { return false; } + /** + * 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. + * + * <p>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}. + * + * <p>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"); + return waitFor(TimeUnit.NANOSECONDS.convert(duration), TimeUnit.NANOSECONDS); + } + /** * Returns the exit value for the process. * @@ -577,8 +607,8 @@ public abstract class Process { /** * This is called from the default implementation of - * {@code waitFor(long, TimeUnit)}, which is specified to poll - * {@code exitValue()}. + * {@code waitFor(long, TimeUnit)} and {@code waitFor(Duration)}, + * which are specified to poll {@code exitValue()}. */ private boolean hasExited() { try { diff --git a/test/jdk/java/lang/Process/WaitForDuration.java b/test/jdk/java/lang/Process/WaitForDuration.java new file mode 100644 index 00000000000..d9172b305a0 --- /dev/null +++ b/test/jdk/java/lang/Process/WaitForDuration.java @@ -0,0 +1,73 @@ +/* + * 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) + * @library /test/lib + * @run junit WaitForDuration + */ + +import java.io.IOException; +import java.time.Duration; +import java.util.stream.Stream; +import jdk.test.lib.process.ProcessTools; + +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<Arguments> durations() { + return Stream.of( + Arguments.of(Duration.ZERO, 3_600_000, false), + Arguments.of(Duration.ofSeconds(-100), 3_600_000, false), + Arguments.of(Duration.ofSeconds(100), 0, true), + Arguments.of(Duration.ofSeconds(Long.MAX_VALUE), 0, true), // nano overflow + Arguments.of(Duration.ofSeconds(Long.MIN_VALUE), 3_600_000, false) // nano underflow + ); + } + + @ParameterizedTest + @MethodSource("durations") + void testEdgeDurations(Duration d, int sleepMillis, boolean expected) + throws IOException, InterruptedException { + var pb = ProcessTools.createTestJavaProcessBuilder( + WaitForDuration.class.getSimpleName(), Integer.toString(sleepMillis)); + assertEquals(expected, pb.start().waitFor(d)); + } + + @Test + void testNullDuration() throws IOException, InterruptedException { + var pb = ProcessTools.createTestJavaProcessBuilder( + WaitForDuration.class.getSimpleName(), "0"); + assertThrows(NullPointerException.class, () -> pb.start().waitFor(null)); + } + + public static void main(String... args) throws InterruptedException { + Thread.sleep(Integer.parseInt(args[0])); + } +} diff --git a/test/lib/jdk/test/lib/process/ProcessTools.java b/test/lib/jdk/test/lib/process/ProcessTools.java index aafb6fe3f4c..6ab36c8bfd3 100644 --- a/test/lib/jdk/test/lib/process/ProcessTools.java +++ b/test/lib/jdk/test/lib/process/ProcessTools.java @@ -42,6 +42,7 @@ import java.nio.file.Paths; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -965,6 +966,15 @@ public final class ProcessTools { return rslt; } + @Override + public boolean waitFor(Duration duration) throws InterruptedException { + boolean rslt = p.waitFor(duration); + if (rslt) { + waitForStreams(); + } + return rslt; + } + private void waitForStreams() throws InterruptedException { try { stdoutTask.get();