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. ProcessThread()120 ProcessThread () 121 { 122 } 123 run()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 spawn(VMProcess 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 VMProcess(String[] cmd, String[] env, File dir, boolean redirect)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. setProcessInfo(OutputStream stdin, InputStream stdout, InputStream stderr, long pid)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 */ exec(String[] cmd, String[] env, File dir)305 static Process exec(String[] cmd, String[] env, File dir) throws IOException 306 { 307 return new VMProcess(cmd, env, dir, false); 308 } 309 exec(List cmd, Map env, File dir, boolean redirect)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 getOutputStream()327 public OutputStream getOutputStream() 328 { 329 return stdin; 330 } 331 getInputStream()332 public InputStream getInputStream() 333 { 334 return stdout; 335 } 336 getErrorStream()337 public InputStream getErrorStream() 338 { 339 return stderr; 340 } 341 waitFor()342 public synchronized int waitFor() throws InterruptedException 343 { 344 while (state != TERMINATED) 345 wait(); 346 return exitValue; 347 } 348 exitValue()349 public synchronized int exitValue() 350 { 351 if (state != TERMINATED) 352 throw new IllegalThreadStateException(); 353 return exitValue; 354 } 355 destroy()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 */ nativeSpawn(String[] cmd, String[] env, File dir, boolean redirect)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. nativeReap()396 static native boolean nativeReap(); 397 398 /** 399 * Kill a process. This sends it a fatal signal but does not reap it. 400 */ nativeKill(long pid)401 private static native void nativeKill(long pid); 402 } 403