re PR libgcj/11801 (Problems with Process.waitFor() and exitValue())
2004-08-12 David Daney <ddaney@avtrex.com> PR libgcj/11801 * java/lang/PosixProcess.java: Rewrote. * java/lang/natPosixProcess.cc: Rewrote. * java/lang/Runtime.java (execInternal): Declare throws IOException. * gcj/javaprims.h (ConcreteProcess$ProcessManager): Declare. * posix-threads.cc (block_sigchld) New function. (_Jv_ThreadRegister) Use it. (_Jv_ThreadStart) Use it. * configure.in (PLATFORM_INNER_NAT_HDRS): New AC_SUBST() used in... * Makefile.am: ... to specify extra native headers. * configure: Regenerated. * include/config.h: Regenerated. * Makefile.in: Regenerated. * gcj/Makefile.in: Regenerated. * include/Makefile.in: Regenerated. * testsuite/Makefile.in: Regenerated. From-SVN: r85880
This commit is contained in:
parent
db151e9d83
commit
c58f29001d
13 changed files with 1218 additions and 628 deletions
|
@ -1,6 +1,5 @@
|
|||
// PosixProcess.java - Subclass of Process for POSIX systems.
|
||||
|
||||
/* Copyright (C) 1998, 1999 Free Software Foundation
|
||||
/* Copyright (C) 1998, 1999, 2004 Free Software Foundation
|
||||
|
||||
This file is part of libgcj.
|
||||
|
||||
|
@ -11,76 +10,466 @@ details. */
|
|||
package java.lang;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
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
|
||||
{
|
||||
public native void destroy ();
|
||||
|
||||
public int exitValue ()
|
||||
static class ProcessManager extends Thread
|
||||
{
|
||||
if (! hasExited)
|
||||
throw new IllegalThreadStateException("Process has not exited");
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public InputStream getErrorStream ()
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
return errorStream;
|
||||
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 InputStream getInputStream ()
|
||||
public synchronized InputStream getErrorStream()
|
||||
{
|
||||
return inputStream;
|
||||
if (returnedErrorStream != null)
|
||||
return returnedErrorStream;
|
||||
|
||||
if (errorStream == null)
|
||||
returnedErrorStream = EOFInputStream.instance;
|
||||
else
|
||||
returnedErrorStream = errorStream;
|
||||
|
||||
return returnedErrorStream;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream ()
|
||||
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 native int waitFor () throws InterruptedException;
|
||||
public int waitFor() throws InterruptedException
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
while (state != STATE_TERMINATED)
|
||||
wait();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// This is used for actual initialization, as we can't write a
|
||||
// native constructor.
|
||||
public native void startProcess (String[] progarray,
|
||||
String[] envp,
|
||||
File dir)
|
||||
throws IOException;
|
||||
/**
|
||||
* 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.
|
||||
public ConcreteProcess (String[] progarray,
|
||||
String[] envp,
|
||||
File dir)
|
||||
throws IOException
|
||||
ConcreteProcess(String[] progarray, String[] envp, File dir)
|
||||
throws IOException
|
||||
{
|
||||
startProcess (progarray, envp, dir);
|
||||
// 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.
|
||||
}
|
||||
|
||||
// The process id. This is cast to a pid_t on the native side.
|
||||
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;
|
||||
|
||||
// True when child has exited.
|
||||
private boolean hasExited;
|
||||
// FIXME: Why doesn't the friend declaration in ConcreteProcess.h
|
||||
// allow ConcreteProcess$ProcessManager native code access these
|
||||
// when they are private?
|
||||
|
||||
// The exit status, if the child has exited.
|
||||
private int status;
|
||||
/** Before the process is forked. */
|
||||
static final int STATE_WAITING_TO_START = 0;
|
||||
|
||||
// The streams.
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue