CloseRace.java revision 15491:6f390eafc676
1/* 2 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24/** 25 * @test 26 * @bug 8024521 27 * @summary Closing ProcessPipeInputStream at the time the process exits is racy 28 * and leads to data corruption. Run this test manually (as 29 * an ordinary java program) with -Xmx8M to repro bug 8024521. 30 * @run main/othervm -Xmx8M -Dtest.duration=2 CloseRace 31 */ 32 33import java.io.*; 34import java.util.ArrayList; 35import java.util.List; 36import java.util.Map; 37import java.util.concurrent.CountDownLatch; 38 39public class CloseRace { 40 private static final String BIG_FILE = "bigfile"; 41 42 private static final int[] procFDs = new int[6]; 43 44 /** default value sufficient to repro bug 8024521. */ 45 private static final int testDurationSeconds 46 = Integer.getInteger("test.duration", 600); 47 48 private static final CountDownLatch threadsStarted 49 = new CountDownLatch(2); 50 51 static boolean fdInUse(int i) { 52 return new File("/proc/self/fd/" + i).exists(); 53 } 54 55 static boolean[] procFDsInUse() { 56 boolean[] inUse = new boolean[procFDs.length]; 57 for (int i = 0; i < procFDs.length; i++) 58 inUse[i] = fdInUse(procFDs[i]); 59 return inUse; 60 } 61 62 static int count(boolean[] bits) { 63 int count = 0; 64 for (int i = 0; i < bits.length; i++) 65 count += bits[i] ? 1 : 0; 66 return count; 67 } 68 69 static void dumpAllStacks() { 70 System.err.println("Start of dump"); 71 final Map<Thread, StackTraceElement[]> allStackTraces 72 = Thread.getAllStackTraces(); 73 for (Thread thread : allStackTraces.keySet()) { 74 System.err.println("Thread " + thread.getName()); 75 for (StackTraceElement element : allStackTraces.get(thread)) 76 System.err.println("\t" + element); 77 } 78 System.err.println("End of dump"); 79 } 80 81 public static void main(String args[]) throws Exception { 82 if (!(new File("/proc/self/fd").isDirectory())) 83 return; 84 85 // Catch Errors from process reaper 86 Thread.setDefaultUncaughtExceptionHandler 87 ((t, e) -> { e.printStackTrace(); System.exit(1); }); 88 89 try (RandomAccessFile f = new RandomAccessFile(BIG_FILE, "rw")) { 90 f.setLength(Runtime.getRuntime().maxMemory()); // provoke OOME 91 } 92 93 for (int i = 0, j = 0; j < procFDs.length; i++) 94 if (!fdInUse(i)) 95 procFDs[j++] = i; 96 97 Thread[] threads = { 98 new Thread(new OpenLoop()), 99 new Thread(new ExecLoop()), 100 }; 101 for (Thread thread : threads) 102 thread.start(); 103 104 threadsStarted.await(); 105 Thread.sleep(testDurationSeconds * 1000); 106 107 for (Thread thread : threads) 108 thread.interrupt(); 109 for (Thread thread : threads) { 110 thread.join(10_000); 111 if (thread.isAlive()) { 112 dumpAllStacks(); 113 throw new Error("At least one child thread (" 114 + thread.getName() 115 + ") failed to finish gracefully"); 116 } 117 } 118 } 119 120 static class OpenLoop implements Runnable { 121 public void run() { 122 threadsStarted.countDown(); 123 while (!Thread.interrupted()) { 124 try { 125 // wait for ExecLoop to finish creating process 126 do { 127 if (Thread.interrupted()) 128 return; 129 } while (count(procFDsInUse()) != 3); 130 List<InputStream> iss = new ArrayList<>(4); 131 132 // eat up three "holes" (closed ends of pipe fd pairs) 133 for (int i = 0; i < 3; i++) 134 iss.add(new FileInputStream(BIG_FILE)); 135 do { 136 if (Thread.interrupted()) 137 return; 138 } while (count(procFDsInUse()) == procFDs.length); 139 // hopefully this will racily occupy empty fd slot 140 iss.add(new FileInputStream(BIG_FILE)); 141 Thread.sleep(1); // Widen race window 142 for (InputStream is : iss) 143 is.close(); 144 } catch (InterruptedException e) { 145 break; 146 } catch (Exception e) { 147 throw new Error(e); 148 } 149 } 150 } 151 } 152 153 static class ExecLoop implements Runnable { 154 public void run() { 155 threadsStarted.countDown(); 156 ProcessBuilder builder = new ProcessBuilder("/bin/true"); 157 while (!Thread.interrupted()) { 158 try { 159 // wait for OpenLoop to finish 160 do { 161 if (Thread.interrupted()) 162 return; 163 } while (count(procFDsInUse()) > 0); 164 Process process = builder.start(); 165 InputStream is = process.getInputStream(); 166 process.waitFor(); 167 is.close(); 168 } catch (InterruptedException e) { 169 break; 170 } catch (Exception e) { 171 throw new Error(e); 172 } 173 } 174 } 175 } 176} 177