1/* 2 * Copyright (c) 2003, 2017, 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/* @test 25 @bug 4843136 4763384 8044841 26 @summary Various race conditions caused exec'ed processes to have 27 extra unused file descriptors, which caused hard-to-reproduce hangs. 28 @author Martin Buchholz 29*/ 30 31import java.util.Timer; 32import java.util.TimerTask; 33import java.io.IOException; 34 35public class SleepyCat { 36 37 private static void destroy (Process[] deathRow) { 38 for (int i = 0; i < deathRow.length; ++i) 39 if (deathRow[i] != null) 40 deathRow[i].destroy(); 41 } 42 43 static class TimeoutTask extends TimerTask { 44 private Process[] deathRow; 45 private boolean timedOut; 46 47 TimeoutTask (Process[] deathRow) { 48 this.deathRow = deathRow; 49 this.timedOut = false; 50 } 51 52 public void run() { 53 dumpState(deathRow); // before killing the processes dump all the state 54 55 timedOut = true; 56 destroy(deathRow); 57 } 58 59 public boolean timedOut() { 60 return timedOut; 61 } 62 } 63 64 /** 65 * Temporary debugging code for intermittent failures. 66 * @param pids the processes to dump status for 67 */ 68 static void dumpState(Process... pids) { 69 if (!System.getProperty("os.name").contains("SunOS")) { 70 return; 71 } 72 try { 73 String[] psArgs = {"ps", "-elf"}; 74 Process ps = new ProcessBuilder(psArgs).inheritIO().start(); 75 ps.waitFor(); 76 String[] sfiles = {"pfiles", "self"}; 77 Process fds = new ProcessBuilder(sfiles).inheritIO().start(); 78 fds.waitFor(); 79 80 for (Process p : pids) { 81 if (p == null) 82 continue; 83 String[] pfiles = {"pfiles", Long.toString(p.pid())}; 84 fds = new ProcessBuilder(pfiles).inheritIO().start(); 85 fds.waitFor(); 86 String[] pstack = {"pstack", Long.toString(p.pid())}; 87 fds = new ProcessBuilder(pstack).inheritIO().start(); 88 fds.waitFor(); 89 } 90 } catch (IOException | InterruptedException i) { 91 i.printStackTrace(); 92 } 93 } 94 95 private static boolean hang1() throws IOException, InterruptedException { 96 // Time out was reproducible on Solaris 50% of the time; 97 // on Linux 80% of the time. 98 // 99 // Scenario: After fork(), parent executes and closes write end of child's stdin. 100 // This causes child to retain a write end of the same pipe. 101 // Thus the child will never see an EOF on its stdin, and will hang. 102 Runtime rt = Runtime.getRuntime(); 103 // Increasing the iteration count makes the bug more 104 // reproducible not only for the obvious reason, but also for 105 // the subtle reason that it makes reading /proc/getppid()/fd 106 // slower, making the child more likely to win the race! 107 int iterations = 20; 108 int timeout = 30; 109 String[] catArgs = new String[] {UnixCommands.cat()}; 110 String[] sleepArgs = new String[] {UnixCommands.sleep(), 111 String.valueOf(timeout+1)}; 112 Process[] cats = new Process[iterations]; 113 Process[] sleeps = new Process[iterations]; 114 Timer timer = new Timer(true); 115 TimeoutTask catExecutioner = new TimeoutTask(cats); 116 timer.schedule(catExecutioner, timeout * 1000); 117 118 for (int i = 0; i < cats.length; ++i) { 119 cats[i] = rt.exec(catArgs); 120 java.io.OutputStream s = cats[i].getOutputStream(); 121 Process sleep = rt.exec(sleepArgs); 122 s.close(); // race condition here 123 sleeps[i] = sleep; 124 } 125 126 for (int i = 0; i < cats.length; ++i) 127 cats[i].waitFor(); // hangs? 128 129 timer.cancel(); 130 131 destroy(sleeps); 132 133 if (catExecutioner.timedOut()) 134 System.out.println("Child process has a hidden writable pipe fd for its stdin."); 135 return catExecutioner.timedOut(); 136 } 137 138 private static boolean hang2() throws Exception { 139 // Inspired by the imaginative test case for 140 // 4850368 (process) getInputStream() attaches to forked background processes (Linux) 141 142 // Time out was reproducible on Linux 80% of the time; 143 // never on Solaris because of explicit close in Solaris-specific code. 144 145 // Scenario: After fork(), the parent naturally closes the 146 // child's stdout write end. The child dup2's the write end 147 // of its stdout onto fd 1. On Linux, it fails to explicitly 148 // close the original fd, and because of the parent's close() 149 // of the fd, the child retains it. The child thus ends up 150 // with two copies of its stdout. Thus closing one of those 151 // write fds does not have the desired effect of causing an 152 // EOF on the parent's read end of that pipe. 153 Runtime rt = Runtime.getRuntime(); 154 int iterations = 10; 155 Timer timer = new Timer(true); 156 int timeout = 30; 157 Process[] backgroundSleepers = new Process[iterations]; 158 TimeoutTask sleeperExecutioner = new TimeoutTask(backgroundSleepers); 159 timer.schedule(sleeperExecutioner, timeout * 1000); 160 byte[] buffer = new byte[10]; 161 String[] args = 162 new String[] {UnixCommands.sh(), "-c", 163 "exec " + UnixCommands.sleep() + " " 164 + (timeout+1) + " >/dev/null"}; 165 166 for (int i = 0; 167 i < backgroundSleepers.length && !sleeperExecutioner.timedOut(); 168 ++i) { 169 backgroundSleepers[i] = rt.exec(args); // race condition here 170 try { 171 // should get immediate EOF, but might hang 172 if (backgroundSleepers[i].getInputStream().read() != -1) 173 throw new Exception("Expected EOF, got a byte"); 174 } catch (IOException e) { 175 // Stream closed by sleeperExecutioner 176 break; 177 } 178 } 179 180 timer.cancel(); 181 182 destroy(backgroundSleepers); 183 184 if (sleeperExecutioner.timedOut()) 185 System.out.println("Child process has two (should be one) writable pipe fds for its stdout."); 186 return sleeperExecutioner.timedOut(); 187 } 188 189 public static void main (String[] args) throws Exception { 190 if (! UnixCommands.isUnix) { 191 System.out.println("For UNIX only"); 192 return; 193 } 194 UnixCommands.ensureCommandsAvailable("sh", "cat", "sleep"); 195 196 if (hang1() | hang2()) 197 throw new Exception("Read from closed pipe hangs"); 198 } 199} 200