Path: blob/master/test/jdk/java/lang/RuntimeTests/exec/SleepyCat.java
41153 views
/*1* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/* @test24@bug 4843136 4763384 804484125@summary Various race conditions caused exec'ed processes to have26extra unused file descriptors, which caused hard-to-reproduce hangs.27@author Martin Buchholz28*/2930import java.util.Timer;31import java.util.TimerTask;32import java.io.IOException;3334public class SleepyCat {3536private static void destroy (Process[] deathRow) {37for (int i = 0; i < deathRow.length; ++i)38if (deathRow[i] != null)39deathRow[i].destroy();40}4142static class TimeoutTask extends TimerTask {43private Process[] deathRow;44private boolean timedOut;4546TimeoutTask (Process[] deathRow) {47this.deathRow = deathRow;48this.timedOut = false;49}5051public void run() {52dumpState(deathRow); // before killing the processes dump all the state5354timedOut = true;55destroy(deathRow);56}5758public boolean timedOut() {59return timedOut;60}61}6263/**64* Temporary debugging code for intermittent failures.65* @param pids the processes to dump status for66*/67static void dumpState(Process... pids) {68if (!System.getProperty("os.name").contains("SunOS")) {69return;70}71try {72String[] psArgs = {"ps", "-elf"};73Process ps = new ProcessBuilder(psArgs).inheritIO().start();74ps.waitFor();75String[] sfiles = {"pfiles", "self"};76Process fds = new ProcessBuilder(sfiles).inheritIO().start();77fds.waitFor();7879for (Process p : pids) {80if (p == null)81continue;82String[] pfiles = {"pfiles", Long.toString(p.pid())};83fds = new ProcessBuilder(pfiles).inheritIO().start();84fds.waitFor();85String[] pstack = {"pstack", Long.toString(p.pid())};86fds = new ProcessBuilder(pstack).inheritIO().start();87fds.waitFor();88}89} catch (IOException | InterruptedException i) {90i.printStackTrace();91}92}9394private static boolean hang1() throws IOException, InterruptedException {95// Time out was reproducible on Solaris 50% of the time;96// on Linux 80% of the time.97//98// Scenario: After fork(), parent executes and closes write end of child's stdin.99// This causes child to retain a write end of the same pipe.100// Thus the child will never see an EOF on its stdin, and will hang.101Runtime rt = Runtime.getRuntime();102// Increasing the iteration count makes the bug more103// reproducible not only for the obvious reason, but also for104// the subtle reason that it makes reading /proc/getppid()/fd105// slower, making the child more likely to win the race!106int iterations = 20;107int timeout = 30;108String[] catArgs = new String[] {UnixCommands.cat()};109String[] sleepArgs = new String[] {UnixCommands.sleep(),110String.valueOf(timeout+1)};111Process[] cats = new Process[iterations];112Process[] sleeps = new Process[iterations];113Timer timer = new Timer(true);114TimeoutTask catExecutioner = new TimeoutTask(cats);115timer.schedule(catExecutioner, timeout * 1000);116117for (int i = 0; i < cats.length; ++i) {118cats[i] = rt.exec(catArgs);119java.io.OutputStream s = cats[i].getOutputStream();120Process sleep = rt.exec(sleepArgs);121s.close(); // race condition here122sleeps[i] = sleep;123}124125for (int i = 0; i < cats.length; ++i)126cats[i].waitFor(); // hangs?127128timer.cancel();129130destroy(sleeps);131132if (catExecutioner.timedOut())133System.out.println("Child process has a hidden writable pipe fd for its stdin.");134return catExecutioner.timedOut();135}136137private static boolean hang2() throws Exception {138// Inspired by the imaginative test case for139// 4850368 (process) getInputStream() attaches to forked background processes (Linux)140141// Time out was reproducible on Linux 80% of the time;142// never on Solaris because of explicit close in Solaris-specific code.143144// Scenario: After fork(), the parent naturally closes the145// child's stdout write end. The child dup2's the write end146// of its stdout onto fd 1. On Linux, it fails to explicitly147// close the original fd, and because of the parent's close()148// of the fd, the child retains it. The child thus ends up149// with two copies of its stdout. Thus closing one of those150// write fds does not have the desired effect of causing an151// EOF on the parent's read end of that pipe.152Runtime rt = Runtime.getRuntime();153int iterations = 10;154Timer timer = new Timer(true);155int timeout = 30;156Process[] backgroundSleepers = new Process[iterations];157TimeoutTask sleeperExecutioner = new TimeoutTask(backgroundSleepers);158timer.schedule(sleeperExecutioner, timeout * 1000);159byte[] buffer = new byte[10];160String[] args =161new String[] {UnixCommands.sh(), "-c",162"exec " + UnixCommands.sleep() + " "163+ (timeout+1) + " >/dev/null"};164165for (int i = 0;166i < backgroundSleepers.length && !sleeperExecutioner.timedOut();167++i) {168backgroundSleepers[i] = rt.exec(args); // race condition here169try {170// should get immediate EOF, but might hang171if (backgroundSleepers[i].getInputStream().read() != -1)172throw new Exception("Expected EOF, got a byte");173} catch (IOException e) {174// Stream closed by sleeperExecutioner175break;176}177}178179timer.cancel();180181destroy(backgroundSleepers);182183if (sleeperExecutioner.timedOut())184System.out.println("Child process has two (should be one) writable pipe fds for its stdout.");185return sleeperExecutioner.timedOut();186}187188public static void main (String[] args) throws Exception {189if (! UnixCommands.isUnix) {190System.out.println("For UNIX only");191return;192}193UnixCommands.ensureCommandsAvailable("sh", "cat", "sleep");194195if (hang1() | hang2())196throw new Exception("Read from closed pipe hangs");197}198}199200201