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