1 /* java.lang.VMProcess -- VM implementation of java.lang.Process
2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 package java.lang;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.OutputStream;
44 import java.util.HashMap;
45 import java.util.Iterator;
46 import java.util.LinkedList;
47 import java.util.List;
48 import java.util.Map;
49
50 /**
51 * Represents one external process. Each instance of this class is in
52 * one of three states: INITIAL, RUNNING, or TERMINATED. The instance
53 * is {@link Object#notifyAll notifyAll()}'d each time the state changes.
54 * The state of all instances is managed by a single dedicated thread
55 * which does the actual fork()/exec() and wait() system calls. User
56 * threads {@link Object#wait()} on the instance when creating the
57 * process or waiting for it to terminate.
58 *
59 * <p>
60 * See
61 * <a href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11801">GCC bug
62 * #11801</a> for the motivation behind the design of this class.
63 *
64 * @author Archie Cobbs
65 * @see Process
66 * @see Runtime#exec(String)
67 */
68 final class VMProcess extends Process
69 {
70
71 // Possible states for a VMProcess
72 private static final int INITIAL = 0;
73 private static final int RUNNING = 1;
74 private static final int TERMINATED = 2;
75
76 // Dedicated thread that does all the fork()'ing and wait()'ing.
77 static Thread processThread;
78
79 // New processes waiting to be spawned by processThread.
80 static final LinkedList workList = new LinkedList();
81
82 // Return values set by nativeReap() when a child is reaped.
83 // These are only accessed by processThread so no locking required.
84 static long reapedPid;
85 static int reapedExitValue;
86
87 // Information about this process
88 int state; // current state of process
89 final String[] cmd; // copied from Runtime.exec()
90 final String[] env; // copied from Runtime.exec()
91 final File dir; // copied from Runtime.exec()
92 Throwable exception; // if process failed to start
93 long pid; // process id
94 OutputStream stdin; // process input stream
95 InputStream stdout; // process output stream
96 InputStream stderr; // process error stream
97 int exitValue; // process exit value
98 boolean redirect; // redirect stderr -> stdout
99
100 //
101 // Dedicated thread that does all the fork()'ing and wait()'ing
102 // for external processes. This is needed because some systems like
103 // Linux use a process-per-thread model, which means the same thread
104 // that did the fork()/exec() must also do the wait().
105 //
106 private static class ProcessThread extends Thread
107 {
108
109 // Max time (in ms) we'll delay before trying to reap another child.
110 private static final int MAX_REAP_DELAY = 1000;
111
112 // Processes created but not yet terminated; maps Long(pid) -> VMProcess
113 // Only used in run() and spawn() method from this Thread, so no locking.
114 private final HashMap activeMap = new HashMap();
115
116 // We have an explicit constructor, because the default
117 // constructor will be private, which means the compiler will have
118 // to generate a second package-private constructor, which is
119 // bogus.
120 ProcessThread ()
121 {
122 }
123
124 public void run()
125 {
126 final LinkedList workList = VMProcess.workList;
127 while (true)
128 {
129
130 // Get the next process to spawn (if any) and spawn it. Spawn
131 // at most one at a time before checking for reapable children.
132 VMProcess process = null;
133 synchronized (workList)
134 {
135 if (!workList.isEmpty())
136 process = (VMProcess)workList.removeFirst();
137 }
138
139 if (process != null)
140 spawn(process);
141
142
143 // Check for termination of active child processes
144 while (!activeMap.isEmpty() && VMProcess.nativeReap())
145 {
146 long pid = VMProcess.reapedPid;
147 int exitValue = VMProcess.reapedExitValue;
148 process = (VMProcess)activeMap.remove(new Long(pid));
149 if (process != null)
150 {
151 synchronized (process)
152 {
153 process.exitValue = exitValue;
154 process.state = TERMINATED;
155 process.notify();
156 }
157 }
158 else
159 System.err.println("VMProcess WARNING reaped unknown process: "
160 + pid);
161 }
162
163
164 // If there are more new processes to create, go do that now.
165 // If there is nothing left to do, exit this thread. Otherwise,
166 // sleep a little while, and then check again for reapable children.
167 // We will get woken up immediately if there are new processes to
168 // spawn, but not if there are new children to reap. So we only
169 // sleep a short time, in effect polling while processes are active.
170 synchronized (workList)
171 {
172 if (!workList.isEmpty())
173 continue;
174 if (activeMap.isEmpty())
175 {
176 processThread = null;
177 break;
178 }
179
180 try
181 {
182 workList.wait(MAX_REAP_DELAY);
183 }
184 catch (InterruptedException e)
185 {
186 /* ignore */
187 }
188 }
189 }
190 }
191
192 // Spawn a process
193 private void spawn(VMProcess process)
194 {
195
196 // Spawn the process and put it in our active map indexed by pid.
197 // If the spawn operation fails, store the exception with the process.
198 // In either case, wake up thread that created the process.
199 synchronized (process)
200 {
201 try
202 {
203 process.nativeSpawn(process.cmd, process.env, process.dir,
204 process.redirect);
205 process.state = RUNNING;
206 activeMap.put(new Long(process.pid), process);
207 }
208 catch (ThreadDeath death)
209 {
210 throw death;
211 }
212 catch (Throwable t)
213 {
214 process.state = TERMINATED;
215 process.exception = t;
216 }
217 process.notify();
218 }
219 }
220 }
221
222 // Constructor
223 private VMProcess(String[] cmd, String[] env, File dir, boolean redirect)
224 throws IOException
225 {
226
227 // Initialize this process
228 this.state = INITIAL;
229 this.cmd = cmd;
230 this.env = env;
231 this.dir = dir;
232 this.redirect = redirect;
233
234 // Add process to the new process work list and wakeup processThread
235 synchronized (workList)
236 {
237 workList.add(this);
238 if (processThread == null)
239 {
240 processThread = new ProcessThread();
241 processThread.setDaemon(true);
242 processThread.start();
243 }
244 else
245 {
246 workList.notify();
247 }
248 }
249
250 // Wait for processThread to spawn this process and update its state
251 synchronized (this)
252 {
253 while (state == INITIAL)
254 {
255 try
256 {
257 wait();
258 }
259 catch (InterruptedException e)
260 {
261 /* ignore */
262 }
263 }
264 }
265
266 // If spawning failed, rethrow the exception in this thread
267 if (exception != null)
268 {
269 exception.fillInStackTrace();
270 if (exception instanceof IOException)
271 throw (IOException)exception;
272
273 if (exception instanceof Error)
274 throw (Error)exception;
275
276 if (exception instanceof RuntimeException)
277 throw (RuntimeException)exception;
278
279 throw new RuntimeException(exception);
280 }
281 }
282
283 // Invoked by native code (from nativeSpawn()) to record process info.
284 private void setProcessInfo(OutputStream stdin,
285 InputStream stdout, InputStream stderr, long pid)
286 {
287 this.stdin = stdin;
288 this.stdout = stdout;
289 if (stderr == null)
290 this.stderr = new InputStream()
291 {
292 public int read() throws IOException
293 {
294 return -1;
295 }
296 };
297 else
298 this.stderr = stderr;
299 this.pid = pid;
300 }
301
302 /**
303 * Entry point from Runtime.exec().
304 */
305 static Process exec(String[] cmd, String[] env, File dir) throws IOException
306 {
307 return new VMProcess(cmd, env, dir, false);
308 }
309
310 static Process exec(List cmd, Map env,
311 File dir, boolean redirect) throws IOException
312 {
313 String[] acmd = (String[]) cmd.toArray(new String[cmd.size()]);
314 String[] aenv = new String[env.size()];
315
316 int i = 0;
317 Iterator iter = env.entrySet().iterator();
318 while (iter.hasNext())
319 {
320 Map.Entry entry = (Map.Entry) iter.next();
321 aenv[i++] = entry.getKey() + "=" + entry.getValue();
322 }
323
324 return new VMProcess(acmd, aenv, dir, redirect);
325 }
326
327 public OutputStream getOutputStream()
328 {
329 return stdin;
330 }
331
332 public InputStream getInputStream()
333 {
334 return stdout;
335 }
336
337 public InputStream getErrorStream()
338 {
339 return stderr;
340 }
341
342 public synchronized int waitFor() throws InterruptedException
343 {
344 while (state != TERMINATED)
345 wait();
346 return exitValue;
347 }
348
349 public synchronized int exitValue()
350 {
351 if (state != TERMINATED)
352 throw new IllegalThreadStateException();
353 return exitValue;
354 }
355
356 public synchronized void destroy()
357 {
358 if (state == TERMINATED)
359 return;
360
361 nativeKill(pid);
362
363 while (state != TERMINATED)
364 {
365 try
366 {
367 wait();
368 }
369 catch (InterruptedException e)
370 {
371 /* ignore */
372 }
373 }
374 }
375
376 /**
377 * Does the fork()/exec() thing to create the O/S process.
378 * Must invoke setProcessInfo() before returning successfully.
379 * This method is only invoked by processThread.
380 *
381 * @throws IOException if the O/S process could not be created.
382 */
383 native void nativeSpawn(String[] cmd, String[] env, File dir,
384 boolean redirect)
385 throws IOException;
386
387 /**
388 * Test for a reapable child process, and reap if so. Does not block.
389 * If a child was reaped, this method must set reapedPid and
390 * reapedExitValue appropriately before returning.
391 * This method is only invoked by processThread.
392 *
393 * @return true if a child was reaped, otherwise false
394 */
395 // This is not private as it is called from an inner class.
396 static native boolean nativeReap();
397
398 /**
399 * Kill a process. This sends it a fatal signal but does not reap it.
400 */
401 private static native void nativeKill(long pid);
402 }