| // PosixProcess.java - Subclass of Process for POSIX systems. |
| /* Copyright (C) 1998, 1999, 2004 Free Software Foundation |
| |
| This file is part of libgcj. |
| |
| This software is copyrighted work licensed under the terms of the |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for |
| details. */ |
| |
| package java.lang; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| |
| /** |
| * @author Tom Tromey <tromey@cygnus.com> |
| * @date May 3, 1999 |
| * @author David Daney <ddaney@avtrex.com> Rewrote using |
| * ProcessManager |
| */ |
| |
| // This is entirely internal to our implementation. |
| // This file is copied to `ConcreteProcess.java' before compilation. |
| // Hence the class name apparently does not match the file name. |
| final class ConcreteProcess extends Process |
| { |
| static class ProcessManager extends Thread |
| { |
| /** |
| * A list of {@link ConcreteProcess ConcreteProcesses} to be |
| * started. The queueLock object is used as the lock Object |
| * for all process related operations. To avoid dead lock |
| * ensure queueLock is obtained before ConcreteProcess. |
| */ |
| List queue = new LinkedList(); |
| private Map pidToProcess = new HashMap(); |
| private boolean ready = false; |
| private long reaperPID; |
| |
| ProcessManager() |
| { |
| super("ProcessManager"); |
| // Don't keep the (main) process from exiting on our account. |
| this.setDaemon(true); |
| } |
| |
| /** |
| * Get the ConcreteProcess object with the given pid and |
| * remove it from the map. This method is called from the |
| * native code for {@link #reap()). The mapping is removed so |
| * the ConcreteProcesses can be GCed after they terminate. |
| * |
| * @param p The pid of the process. |
| */ |
| private ConcreteProcess removeProcessFromMap(long p) |
| { |
| return (ConcreteProcess) pidToProcess.remove(new Long(p)); |
| } |
| |
| /** |
| * Put the given ConcreteProcess in the map using the Long |
| * value of its pid as the key. |
| * |
| * @param p The ConcreteProcess. |
| */ |
| void addProcessToMap(ConcreteProcess p) |
| { |
| pidToProcess.put(new Long(p.pid), p); |
| } |
| |
| /** |
| * Queue up the ConcreteProcess and awake the ProcessManager. |
| * The ProcessManager will start the ConcreteProcess from its |
| * thread so it can be reaped when it terminates. |
| * |
| * @param p The ConcreteProcess. |
| */ |
| void startExecuting(ConcreteProcess p) |
| { |
| synchronized (queueLock) |
| { |
| queue.add(p); |
| signalReaper(); // If blocked in waitForSignal(). |
| queueLock.notifyAll(); // If blocked in wait(); |
| } |
| } |
| |
| /** |
| * Block until the ProcessManager thread is ready to accept |
| * commands. |
| */ |
| void waitUntilReady() |
| { |
| synchronized (this) |
| { |
| try |
| { |
| while (! ready) |
| wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // Ignore. |
| } |
| } |
| } |
| |
| /** |
| * Main Process starting/reaping loop. |
| */ |
| public void run() |
| { |
| init(); |
| // Now ready to accept requests. |
| synchronized (this) |
| { |
| ready = true; |
| this.notifyAll(); |
| } |
| |
| for (;;) |
| { |
| try |
| { |
| synchronized (queueLock) |
| { |
| boolean haveMoreChildren = reap(); |
| if (! haveMoreChildren && queue.size() == 0) |
| { |
| // This reaper thread could exit, but we |
| // keep it alive for a while in case |
| // someone wants to start more Processes. |
| try |
| { |
| queueLock.wait(1000L); |
| if (queue.size() == 0) |
| { |
| processManager = null; |
| return; // Timed out. |
| } |
| } |
| catch (InterruptedException ie) |
| { |
| // Ignore and exit the thread. |
| return; |
| } |
| } |
| while (queue.size() > 0) |
| { |
| ConcreteProcess p = (ConcreteProcess) queue.remove(0); |
| p.spawn(this); |
| } |
| } |
| |
| // Wait for a SIGCHLD from either an exiting |
| // process or the startExecuting() method. This |
| // is done outside of the synchronized block to |
| // allow other threads to enter and submit more |
| // jobs. |
| waitForSignal(); |
| } |
| catch (Exception ex) |
| { |
| ex.printStackTrace(System.err); |
| } |
| } |
| } |
| |
| /** |
| * Setup native signal handlers and other housekeeping things. |
| * |
| */ |
| private native void init(); |
| |
| /** |
| * Block waiting for SIGCHLD. |
| * |
| */ |
| private native void waitForSignal(); |
| |
| /** |
| * Try to reap as many children as possible without blocking. |
| * |
| * @return true if more live children exist. |
| * |
| */ |
| private native boolean reap(); |
| |
| /** |
| * Send SIGCHLD to the reaper thread. |
| */ |
| private native void signalReaper(); |
| } |
| |
| public void destroy() |
| { |
| // Synchronized on the queueLock. This ensures that the reaper |
| // thread cannot be doing a wait() on the child. |
| // Otherwise there would be a race where the OS could |
| // create a process with the same pid between the wait() |
| // and the update of the state which would cause a kill to |
| // the wrong process. |
| synchronized (queueLock) |
| { |
| synchronized (this) |
| { |
| // If there is no ProcessManager we cannot kill. |
| if (state != STATE_TERMINATED) |
| { |
| if (processManager == null) |
| throw new InternalError(); |
| nativeDestroy(); |
| } |
| } |
| } |
| } |
| |
| private native void nativeDestroy(); |
| |
| public int exitValue() |
| { |
| synchronized (this) |
| { |
| if (state != STATE_TERMINATED) |
| throw new IllegalThreadStateException("Process has not exited"); |
| } |
| return status; |
| } |
| |
| /** |
| * Called by native code when process exits. |
| * |
| * Already synchronized (this). Close any streams that we can to |
| * conserve file descriptors. |
| * |
| * The outputStream can be closed as any future writes will |
| * generate an IOException due to EPIPE. |
| * |
| * The inputStream and errorStream can only be closed if the user |
| * has not obtained a reference to them AND they have no bytes |
| * available. Since the process has terminated they will never have |
| * any more data available and can safely be replaced by |
| * EOFInputStreams. |
| */ |
| void processTerminationCleanup() |
| { |
| try |
| { |
| outputStream.close(); |
| } |
| catch (IOException ioe) |
| { |
| // Ignore. |
| } |
| try |
| { |
| if (returnedErrorStream == null && errorStream.available() == 0) |
| { |
| errorStream.close(); |
| errorStream = null; |
| } |
| } |
| catch (IOException ioe) |
| { |
| // Ignore. |
| } |
| try |
| { |
| if (returnedInputStream == null && inputStream.available() == 0) |
| { |
| inputStream.close(); |
| inputStream = null; |
| } |
| } |
| catch (IOException ioe) |
| { |
| // Ignore. |
| } |
| } |
| |
| public synchronized InputStream getErrorStream() |
| { |
| if (returnedErrorStream != null) |
| return returnedErrorStream; |
| |
| if (errorStream == null) |
| returnedErrorStream = EOFInputStream.instance; |
| else |
| returnedErrorStream = errorStream; |
| |
| return returnedErrorStream; |
| } |
| |
| public synchronized InputStream getInputStream() |
| { |
| if (returnedInputStream != null) |
| return returnedInputStream; |
| |
| if (inputStream == null) |
| returnedInputStream = EOFInputStream.instance; |
| else |
| returnedInputStream = inputStream; |
| |
| return returnedInputStream; |
| } |
| |
| public OutputStream getOutputStream() |
| { |
| return outputStream; |
| } |
| |
| public int waitFor() throws InterruptedException |
| { |
| synchronized (this) |
| { |
| while (state != STATE_TERMINATED) |
| wait(); |
| } |
| return status; |
| } |
| |
| /** |
| * Start this process running. This should only be called by the |
| * ProcessManager. |
| * |
| * @param pm The ProcessManager that made the call. |
| */ |
| void spawn(ProcessManager pm) |
| { |
| synchronized (this) |
| { |
| // Do the fork/exec magic. |
| nativeSpawn(); |
| // There is no race with reap() in the pidToProcess map |
| // because this is always called from the same thread |
| // doing the reaping. |
| pm.addProcessToMap(this); |
| state = STATE_RUNNING; |
| // Notify anybody waiting on state change. |
| this.notifyAll(); |
| } |
| } |
| |
| /** |
| * Do the fork and exec. |
| */ |
| private native void nativeSpawn(); |
| |
| // This file is copied to `ConcreteProcess.java' before |
| // compilation. Hence the constructor name apparently does not |
| // match the file name. |
| ConcreteProcess(String[] progarray, String[] envp, File dir) |
| throws IOException |
| { |
| // Check to ensure there is something to run, and avoid |
| // dereferencing null pointers in native code. |
| if (progarray[0] == null) |
| throw new NullPointerException(); |
| |
| this.progarray = progarray; |
| this.envp = envp; |
| this.dir = dir; |
| |
| // Start a ProcessManager if there is not one already running. |
| synchronized (queueLock) |
| { |
| if (processManager == null) |
| { |
| processManager = new ProcessManager(); |
| processManager.start(); |
| processManager.waitUntilReady(); |
| } |
| |
| // Queue this ConcreteProcess for starting by the ProcessManager. |
| processManager.startExecuting(this); |
| } |
| |
| // Wait until ProcessManager has started us. |
| synchronized (this) |
| { |
| while (state == STATE_WAITING_TO_START) |
| { |
| try |
| { |
| wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // FIXME: What to do when interrupted while blocking in a constructor? |
| // Ignore. |
| } |
| } |
| } |
| |
| // If there was a problem, re-throw it. |
| if (exception != null) |
| { |
| if (exception instanceof IOException) |
| { |
| IOException ioe = new IOException(exception.toString()); |
| ioe.initCause(exception); |
| throw ioe; |
| } |
| |
| // Not an IOException. Something bad happened. |
| InternalError ie = new InternalError(exception.toString()); |
| ie.initCause(exception); |
| throw ie; |
| } |
| |
| // If we get here, all is well, the Process has started. |
| } |
| |
| private String[] progarray; |
| private String[] envp; |
| private File dir; |
| |
| /** Set by the ProcessManager on problems starting. */ |
| private Throwable exception; |
| |
| /** The process id. This is cast to a pid_t on the native side. */ |
| private long pid; |
| |
| // FIXME: Why doesn't the friend declaration in ConcreteProcess.h |
| // allow ConcreteProcess$ProcessManager native code access these |
| // when they are private? |
| |
| /** Before the process is forked. */ |
| static final int STATE_WAITING_TO_START = 0; |
| |
| /** After the fork. */ |
| static final int STATE_RUNNING = 1; |
| |
| /** After exit code has been collected. */ |
| static final int STATE_TERMINATED = 2; |
| |
| /** One of STATE_WAITING_TO_START, STATE_RUNNING, STATE_TERMINATED. */ |
| int state; |
| |
| /** The exit status, if the child has exited. */ |
| int status; |
| |
| /** The streams. */ |
| private InputStream errorStream; |
| private InputStream inputStream; |
| private OutputStream outputStream; |
| |
| /** InputStreams obtained by the user. Not null indicates that the |
| * user has obtained the stream. |
| */ |
| private InputStream returnedErrorStream; |
| private InputStream returnedInputStream; |
| |
| /** |
| * Lock Object for all processManager related locking. |
| */ |
| private static Object queueLock = new Object(); |
| private static ProcessManager processManager; |
| |
| static class EOFInputStream extends InputStream |
| { |
| static EOFInputStream instance = new EOFInputStream(); |
| public int read() |
| { |
| return -1; |
| } |
| } |
| } |