blob: b107c12e4cd4193be933ed36498bf1a7d8d96da5 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 */
23
24/* @test
25 @bug 4843136 4763384
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 timedOut = true;
54 destroy(deathRow);
55 }
56
57 public boolean timedOut() {
58 return timedOut;
59 }
60 }
61
62 private static boolean hang1() throws IOException, InterruptedException {
63 // Time out was reproducible on Solaris 50% of the time;
64 // on Linux 80% of the time.
65 //
66 // Scenario: After fork(), parent executes and closes write end of child's stdin.
67 // This causes child to retain a write end of the same pipe.
68 // Thus the child will never see an EOF on its stdin, and will hang.
69 Runtime rt = Runtime.getRuntime();
70 // Increasing the iteration count makes the bug more
71 // reproducible not only for the obvious reason, but also for
72 // the subtle reason that it makes reading /proc/getppid()/fd
73 // slower, making the child more likely to win the race!
74 int iterations = 20;
75 int timeout = 30;
76 String[] catArgs = new String[] {"/bin/cat"};
77 String[] sleepArgs = new String[] {"/bin/sleep",
78 String.valueOf(timeout+1)};
79 Process[] cats = new Process[iterations];
80 Process[] sleeps = new Process[iterations];
81 Timer timer = new Timer(true);
82 TimeoutTask catExecutioner = new TimeoutTask(cats);
83 timer.schedule(catExecutioner, timeout * 1000);
84
85 for (int i = 0; i < cats.length; ++i) {
86 cats[i] = rt.exec(catArgs);
87 java.io.OutputStream s = cats[i].getOutputStream();
88 Process sleep = rt.exec(sleepArgs);
89 s.close(); // race condition here
90 sleeps[i] = sleep;
91 }
92
93 for (int i = 0; i < cats.length; ++i)
94 cats[i].waitFor(); // hangs?
95
96 timer.cancel();
97
98 destroy(sleeps);
99
100 if (catExecutioner.timedOut())
101 System.out.println("Child process has a hidden writable pipe fd for its stdin.");
102 return catExecutioner.timedOut();
103 }
104
105 private static boolean hang2() throws Exception {
106 // Inspired by the imaginative test case for
107 // 4850368 (process) getInputStream() attaches to forked background processes (Linux)
108
109 // Time out was reproducible on Linux 80% of the time;
110 // never on Solaris because of explicit close in Solaris-specific code.
111
112 // Scenario: After fork(), the parent naturally closes the
113 // child's stdout write end. The child dup2's the write end
114 // of its stdout onto fd 1. On Linux, it fails to explicitly
115 // close the original fd, and because of the parent's close()
116 // of the fd, the child retains it. The child thus ends up
117 // with two copies of its stdout. Thus closing one of those
118 // write fds does not have the desired effect of causing an
119 // EOF on the parent's read end of that pipe.
120 Runtime rt = Runtime.getRuntime();
121 int iterations = 10;
122 Timer timer = new Timer(true);
123 int timeout = 30;
124 Process[] backgroundSleepers = new Process[iterations];
125 TimeoutTask sleeperExecutioner = new TimeoutTask(backgroundSleepers);
126 timer.schedule(sleeperExecutioner, timeout * 1000);
127 byte[] buffer = new byte[10];
128 String[] args =
129 new String[] {"/bin/sh", "-c",
130 "exec sleep " + (timeout+1) + " >/dev/null"};
131
132 for (int i = 0;
133 i < backgroundSleepers.length && !sleeperExecutioner.timedOut();
134 ++i) {
135 backgroundSleepers[i] = rt.exec(args); // race condition here
136 try {
137 // should get immediate EOF, but might hang
138 if (backgroundSleepers[i].getInputStream().read() != -1)
139 throw new Exception("Expected EOF, got a byte");
140 } catch (IOException e) {
141 // Stream closed by sleeperExecutioner
142 break;
143 }
144 }
145
146 timer.cancel();
147
148 destroy(backgroundSleepers);
149
150 if (sleeperExecutioner.timedOut())
151 System.out.println("Child process has two (should be one) writable pipe fds for its stdout.");
152 return sleeperExecutioner.timedOut();
153 }
154
155 public static void main (String[] args) throws Exception {
156 try {
157 if (hang1() | hang2())
158 throw new Exception("Read from closed pipe hangs");
159 } catch (IOException e) {
160 // We will get here on non-Posix systems,
161 // which don't have cat and sleep and sh.
162 }
163 }
164}