|  | /* | 
|  | * Copyright (c) 2013, 2014 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 8024521 | 
|  | * @summary Closing ProcessPipeInputStream at the time the process exits is racy | 
|  | *          and leads to data corruption. Run this test manually (as | 
|  | *          an ordinary java program) with  -Xmx8M  to repro bug 8024521. | 
|  | * @run main/othervm -Xmx8M -Dtest.duration=2 CloseRace | 
|  | */ | 
|  |  | 
|  | import java.io.*; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.concurrent.CountDownLatch; | 
|  |  | 
|  | public class CloseRace { | 
|  | private static final String BIG_FILE = "bigfile"; | 
|  |  | 
|  | private static final int[] procFDs = new int[6]; | 
|  |  | 
|  | /** default value sufficient to repro bug 8024521. */ | 
|  | private static final int testDurationSeconds | 
|  | = Integer.getInteger("test.duration", 600); | 
|  |  | 
|  | private static final CountDownLatch threadsStarted | 
|  | = new CountDownLatch(2); | 
|  |  | 
|  | static boolean fdInUse(int i) { | 
|  | return new File("/proc/self/fd/" + i).exists(); | 
|  | } | 
|  |  | 
|  | static boolean[] procFDsInUse() { | 
|  | boolean[] inUse = new boolean[procFDs.length]; | 
|  | for (int i = 0; i < procFDs.length; i++) | 
|  | inUse[i] = fdInUse(procFDs[i]); | 
|  | return inUse; | 
|  | } | 
|  |  | 
|  | static int count(boolean[] bits) { | 
|  | int count = 0; | 
|  | for (int i = 0; i < bits.length; i++) | 
|  | count += bits[i] ? 1 : 0; | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static void dumpAllStacks() { | 
|  | System.err.println("Start of dump"); | 
|  | final Map<Thread, StackTraceElement[]> allStackTraces | 
|  | = Thread.getAllStackTraces(); | 
|  | for (Thread thread : allStackTraces.keySet()) { | 
|  | System.err.println("Thread " + thread.getName()); | 
|  | for (StackTraceElement element : allStackTraces.get(thread)) | 
|  | System.err.println("\t" + element); | 
|  | } | 
|  | System.err.println("End of dump"); | 
|  | } | 
|  |  | 
|  | public static void main(String args[]) throws Exception { | 
|  | if (!(new File("/proc/self/fd").isDirectory())) | 
|  | return; | 
|  |  | 
|  | // Catch Errors from process reaper | 
|  | Thread.setDefaultUncaughtExceptionHandler | 
|  | ((t, e) -> { e.printStackTrace(); System.exit(1); }); | 
|  |  | 
|  | try (RandomAccessFile f = new RandomAccessFile(BIG_FILE, "rw")) { | 
|  | f.setLength(Runtime.getRuntime().maxMemory()); // provoke OOME | 
|  | } | 
|  |  | 
|  | for (int i = 0, j = 0; j < procFDs.length; i++) | 
|  | if (!fdInUse(i)) | 
|  | procFDs[j++] = i; | 
|  |  | 
|  | Thread[] threads = { | 
|  | new Thread(new OpenLoop()), | 
|  | new Thread(new ExecLoop()), | 
|  | }; | 
|  | for (Thread thread : threads) | 
|  | thread.start(); | 
|  |  | 
|  | threadsStarted.await(); | 
|  | Thread.sleep(testDurationSeconds * 1000); | 
|  |  | 
|  | for (Thread thread : threads) | 
|  | thread.interrupt(); | 
|  | for (Thread thread : threads) { | 
|  | thread.join(10_000); | 
|  | if (thread.isAlive()) { | 
|  | dumpAllStacks(); | 
|  | throw new Error("At least one child thread (" | 
|  | + thread.getName() | 
|  | + ") failed to finish gracefully"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static class OpenLoop implements Runnable { | 
|  | public void run() { | 
|  | threadsStarted.countDown(); | 
|  | while (!Thread.interrupted()) { | 
|  | try { | 
|  | // wait for ExecLoop to finish creating process | 
|  | do { | 
|  | if (Thread.interrupted()) | 
|  | return; | 
|  | } while (count(procFDsInUse()) != 3); | 
|  | List<InputStream> iss = new ArrayList<>(4); | 
|  |  | 
|  | // eat up three "holes" (closed ends of pipe fd pairs) | 
|  | for (int i = 0; i < 3; i++) | 
|  | iss.add(new FileInputStream(BIG_FILE)); | 
|  | do { | 
|  | if (Thread.interrupted()) | 
|  | return; | 
|  | } while (count(procFDsInUse()) == procFDs.length); | 
|  | // hopefully this will racily occupy empty fd slot | 
|  | iss.add(new FileInputStream(BIG_FILE)); | 
|  | Thread.sleep(1); // Widen race window | 
|  | for (InputStream is : iss) | 
|  | is.close(); | 
|  | } catch (InterruptedException e) { | 
|  | break; | 
|  | } catch (Exception e) { | 
|  | throw new Error(e); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static class ExecLoop implements Runnable { | 
|  | public void run() { | 
|  | threadsStarted.countDown(); | 
|  | ProcessBuilder builder = new ProcessBuilder("/bin/true"); | 
|  | while (!Thread.interrupted()) { | 
|  | try { | 
|  | // wait for OpenLoop to finish | 
|  | do { | 
|  | if (Thread.interrupted()) | 
|  | return; | 
|  | } while (count(procFDsInUse()) > 0); | 
|  | Process process = builder.start(); | 
|  | InputStream is = process.getInputStream(); | 
|  | process.waitFor(); | 
|  | is.close(); | 
|  | } catch (InterruptedException e) { | 
|  | break; | 
|  | } catch (Exception e) { | 
|  | throw new Error(e); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |