0N/A/*
2362N/A * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/A/* @test
0N/A @bug 4843136 4763384
0N/A @summary Various race conditions caused exec'ed processes to have
0N/A extra unused file descriptors, which caused hard-to-reproduce hangs.
0N/A @author Martin Buchholz
0N/A*/
0N/A
0N/Aimport java.util.Timer;
0N/Aimport java.util.TimerTask;
0N/Aimport java.io.IOException;
0N/A
0N/Apublic class SleepyCat {
0N/A
0N/A private static void destroy (Process[] deathRow) {
0N/A for (int i = 0; i < deathRow.length; ++i)
0N/A if (deathRow[i] != null)
0N/A deathRow[i].destroy();
0N/A }
0N/A
0N/A static class TimeoutTask extends TimerTask {
0N/A private Process[] deathRow;
0N/A private boolean timedOut;
0N/A
0N/A TimeoutTask (Process[] deathRow) {
0N/A this.deathRow = deathRow;
0N/A this.timedOut = false;
0N/A }
0N/A
0N/A public void run() {
0N/A timedOut = true;
0N/A destroy(deathRow);
0N/A }
0N/A
0N/A public boolean timedOut() {
0N/A return timedOut;
0N/A }
0N/A }
0N/A
0N/A private static boolean hang1() throws IOException, InterruptedException {
0N/A // Time out was reproducible on Solaris 50% of the time;
0N/A // on Linux 80% of the time.
0N/A //
0N/A // Scenario: After fork(), parent executes and closes write end of child's stdin.
0N/A // This causes child to retain a write end of the same pipe.
0N/A // Thus the child will never see an EOF on its stdin, and will hang.
0N/A Runtime rt = Runtime.getRuntime();
0N/A // Increasing the iteration count makes the bug more
0N/A // reproducible not only for the obvious reason, but also for
0N/A // the subtle reason that it makes reading /proc/getppid()/fd
0N/A // slower, making the child more likely to win the race!
0N/A int iterations = 20;
0N/A int timeout = 30;
0N/A String[] catArgs = new String[] {"/bin/cat"};
0N/A String[] sleepArgs = new String[] {"/bin/sleep",
0N/A String.valueOf(timeout+1)};
0N/A Process[] cats = new Process[iterations];
0N/A Process[] sleeps = new Process[iterations];
0N/A Timer timer = new Timer(true);
0N/A TimeoutTask catExecutioner = new TimeoutTask(cats);
0N/A timer.schedule(catExecutioner, timeout * 1000);
0N/A
0N/A for (int i = 0; i < cats.length; ++i) {
0N/A cats[i] = rt.exec(catArgs);
0N/A java.io.OutputStream s = cats[i].getOutputStream();
0N/A Process sleep = rt.exec(sleepArgs);
0N/A s.close(); // race condition here
0N/A sleeps[i] = sleep;
0N/A }
0N/A
0N/A for (int i = 0; i < cats.length; ++i)
0N/A cats[i].waitFor(); // hangs?
0N/A
0N/A timer.cancel();
0N/A
0N/A destroy(sleeps);
0N/A
0N/A if (catExecutioner.timedOut())
0N/A System.out.println("Child process has a hidden writable pipe fd for its stdin.");
0N/A return catExecutioner.timedOut();
0N/A }
0N/A
0N/A private static boolean hang2() throws Exception {
0N/A // Inspired by the imaginative test case for
0N/A // 4850368 (process) getInputStream() attaches to forked background processes (Linux)
0N/A
0N/A // Time out was reproducible on Linux 80% of the time;
0N/A // never on Solaris because of explicit close in Solaris-specific code.
0N/A
0N/A // Scenario: After fork(), the parent naturally closes the
0N/A // child's stdout write end. The child dup2's the write end
0N/A // of its stdout onto fd 1. On Linux, it fails to explicitly
0N/A // close the original fd, and because of the parent's close()
0N/A // of the fd, the child retains it. The child thus ends up
0N/A // with two copies of its stdout. Thus closing one of those
0N/A // write fds does not have the desired effect of causing an
0N/A // EOF on the parent's read end of that pipe.
0N/A Runtime rt = Runtime.getRuntime();
0N/A int iterations = 10;
0N/A Timer timer = new Timer(true);
0N/A int timeout = 30;
0N/A Process[] backgroundSleepers = new Process[iterations];
0N/A TimeoutTask sleeperExecutioner = new TimeoutTask(backgroundSleepers);
0N/A timer.schedule(sleeperExecutioner, timeout * 1000);
0N/A byte[] buffer = new byte[10];
0N/A String[] args =
0N/A new String[] {"/bin/sh", "-c",
0N/A "exec sleep " + (timeout+1) + " >/dev/null"};
0N/A
0N/A for (int i = 0;
0N/A i < backgroundSleepers.length && !sleeperExecutioner.timedOut();
0N/A ++i) {
0N/A backgroundSleepers[i] = rt.exec(args); // race condition here
0N/A try {
0N/A // should get immediate EOF, but might hang
0N/A if (backgroundSleepers[i].getInputStream().read() != -1)
0N/A throw new Exception("Expected EOF, got a byte");
0N/A } catch (IOException e) {
0N/A // Stream closed by sleeperExecutioner
0N/A break;
0N/A }
0N/A }
0N/A
0N/A timer.cancel();
0N/A
0N/A destroy(backgroundSleepers);
0N/A
0N/A if (sleeperExecutioner.timedOut())
0N/A System.out.println("Child process has two (should be one) writable pipe fds for its stdout.");
0N/A return sleeperExecutioner.timedOut();
0N/A }
0N/A
0N/A public static void main (String[] args) throws Exception {
0N/A try {
0N/A if (hang1() | hang2())
0N/A throw new Exception("Read from closed pipe hangs");
0N/A } catch (IOException e) {
0N/A // We will get here on non-Posix systems,
0N/A // which don't have cat and sleep and sh.
0N/A }
0N/A }
0N/A}