java-topology/test/jdk/java/lang/ProcessBuilder/ConcNativeForkTest/ConcNativeForkTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

129 lines
5 KiB
Java

/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* 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 id=POSIX_SPAWN
* @bug 8377907
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
* @requires os.family != "windows"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=POSIX_SPAWN ConcNativeForkTest
*/
/*
* @test id=FORK
* @bug 8377907
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
* @requires os.family != "windows"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=FORK ConcNativeForkTest
*/
/*
* @test id=VFORK
* @bug 8377907
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
* @requires os.family == "linux"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=VFORK ConcNativeForkTest
*/
public class ConcNativeForkTest {
// How this works:
// - We start a child process via ProcessBuilder. Does not matter what, we just call "/bin/true".
// - Concurrently, we continuously (up to a limit) fork natively; these forks will all exec "sleep 30".
// - If the natively forked child process forks off at the right (wrong) moment, it will catch the open pipe from
// the "/bin/true" child process, and forcing the parent process (this test) to wait in ProcessBuilder.start()
// (inside forkAndExec()) until the natively forked child releases the pipe file descriptors it inherited.
// Notes:
//
// Obviously, this is racy and depends on scheduler timings of the underlying OS. The test succeeding is
// no proof the bug does not exist (see PipesCloseOnExecTest as a complimentary test that is more reliable, but
// only works on Linux).
// That said, in tests it reliably reproduces the bug on Linux x64 and MacOS Arm.
//
// This test is not well suited for automatic test execution, since the test essentially
// fork-bombs itself, and that may run into issues in containerized CI/CD environments.
native static boolean prepareNativeForkerThread(int numForks);
native static void releaseNativeForkerThread();
native static void stopNativeForkerThread();
private static final int numIterations = 20;
public static void main(String[] args) throws Exception {
System.out.println("jdk.lang.Process.launchMechanism=" +
System.getProperty("jdk.lang.Process.launchMechanism"));
System.loadLibrary("ConcNativeFork");
// A very simple program returning immediately (/bin/true)
ProcessBuilder pb = new ProcessBuilder("true").inheritIO();
final int numJavaProcesses = 10;
final int numNativeProcesses = 250;
Process[] processes = new Process[numJavaProcesses];
for (int iteration = 0; iteration < numIterations; iteration ++) {
if (!prepareNativeForkerThread(numNativeProcesses)) {
throw new RuntimeException("Failed to start native forker thread (see stdout)");
}
long[] durations = new long[numJavaProcesses];
releaseNativeForkerThread();
for (int np = 0; np < numJavaProcesses; np ++) {
long t1 = System.currentTimeMillis();
try (Process p = pb.start()) {
durations[np] = System.currentTimeMillis() - t1;
processes[np] = p;
}
}
stopNativeForkerThread();
long longestDuration = 0;
for (int np = 0; np < numJavaProcesses; np ++) {
processes[np].waitFor();
System.out.printf("Duration: %dms%n", durations[np]);
longestDuration = Math.max(durations[np], longestDuration);
}
System.out.printf("Longest startup time: %dms%n", longestDuration);
if (longestDuration >= 30000) {
throw new RuntimeException("Looks like we blocked on native fork");
}
}
}
}