369 lines
10 KiB
Java
369 lines
10 KiB
Java
![]() |
/* java.lang.VMProcess -- VM implementation of java.lang.Process
|
||
|
Copyright (C) 2004, 2005 Free Software Foundation, Inc.
|
||
|
|
||
|
This file is part of GNU Classpath.
|
||
|
|
||
|
GNU Classpath is free software; you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation; either version 2, or (at your option)
|
||
|
any later version.
|
||
|
|
||
|
GNU Classpath is distributed in the hope that it will be useful, but
|
||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
||
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
02110-1301 USA.
|
||
|
|
||
|
Linking this library statically or dynamically with other modules is
|
||
|
making a combined work based on this library. Thus, the terms and
|
||
|
conditions of the GNU General Public License cover the whole
|
||
|
combination.
|
||
|
|
||
|
As a special exception, the copyright holders of this library give you
|
||
|
permission to link this library with independent modules to produce an
|
||
|
executable, regardless of the license terms of these independent
|
||
|
modules, and to copy and distribute the resulting executable under
|
||
|
terms of your choice, provided that you also meet, for each linked
|
||
|
independent module, the terms and conditions of the license of that
|
||
|
module. An independent module is a module which is not derived from
|
||
|
or based on this library. If you modify this library, you may extend
|
||
|
this exception to your version of the library, but you are not
|
||
|
obligated to do so. If you do not wish to do so, delete this
|
||
|
exception statement from your version. */
|
||
|
|
||
|
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;
|
||
|
|
||
|
/**
|
||
|
* Represents one external process. Each instance of this class is in
|
||
|
* one of three states: INITIAL, RUNNING, or TERMINATED. The instance
|
||
|
* is {@link Object#notifyAll notifyAll()}'d each time the state changes.
|
||
|
* The state of all instances is managed by a single dedicated thread
|
||
|
* which does the actual fork()/exec() and wait() system calls. User
|
||
|
* threads {@link Object#wait wait()} on the instance when creating the
|
||
|
* process or waiting for it to terminate.
|
||
|
*
|
||
|
* <p>
|
||
|
* See
|
||
|
* <a href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11801">GCC bug
|
||
|
* #11801</a> for the motivation behind the design of this class.
|
||
|
*
|
||
|
* @author Archie Cobbs
|
||
|
* @see Process
|
||
|
* @see Runtime#exec
|
||
|
*/
|
||
|
final class VMProcess extends Process
|
||
|
{
|
||
|
|
||
|
// Possible states for a VMProcess
|
||
|
private static final int INITIAL = 0;
|
||
|
private static final int RUNNING = 1;
|
||
|
private static final int TERMINATED = 2;
|
||
|
|
||
|
// Dedicated thread that does all the fork()'ing and wait()'ing.
|
||
|
static Thread processThread;
|
||
|
|
||
|
// New processes waiting to be spawned by processThread.
|
||
|
static final LinkedList workList = new LinkedList();
|
||
|
|
||
|
// Return values set by nativeReap() when a child is reaped.
|
||
|
// These are only accessed by processThread so no locking required.
|
||
|
static long reapedPid;
|
||
|
static int reapedExitValue;
|
||
|
|
||
|
// Information about this process
|
||
|
int state; // current state of process
|
||
|
final String[] cmd; // copied from Runtime.exec()
|
||
|
final String[] env; // copied from Runtime.exec()
|
||
|
final File dir; // copied from Runtime.exec()
|
||
|
Throwable exception; // if process failed to start
|
||
|
long pid; // process id
|
||
|
OutputStream stdin; // process input stream
|
||
|
InputStream stdout; // process output stream
|
||
|
InputStream stderr; // process error stream
|
||
|
int exitValue; // process exit value
|
||
|
|
||
|
//
|
||
|
// Dedicated thread that does all the fork()'ing and wait()'ing
|
||
|
// for external processes. This is needed because some systems like
|
||
|
// Linux use a process-per-thread model, which means the same thread
|
||
|
// that did the fork()/exec() must also do the wait().
|
||
|
//
|
||
|
private static class ProcessThread extends Thread
|
||
|
{
|
||
|
|
||
|
// Max time (in ms) we'll delay before trying to reap another child.
|
||
|
private static final int MAX_REAP_DELAY = 1000;
|
||
|
|
||
|
// Processes created but not yet terminated; maps Long(pid) -> VMProcess
|
||
|
// Only used in run() and spawn() method from this Thread, so no locking.
|
||
|
private final HashMap activeMap = new HashMap();
|
||
|
|
||
|
// We have an explicit constructor, because the default
|
||
|
// constructor will be private, which means the compiler will have
|
||
|
// to generate a second package-private constructor, which is
|
||
|
// bogus.
|
||
|
ProcessThread ()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public void run()
|
||
|
{
|
||
|
final LinkedList workList = VMProcess.workList;
|
||
|
while (true)
|
||
|
{
|
||
|
|
||
|
// Get the next process to spawn (if any) and spawn it. Spawn
|
||
|
// at most one at a time before checking for reapable children.
|
||
|
VMProcess process = null;
|
||
|
synchronized (workList)
|
||
|
{
|
||
|
if (!workList.isEmpty())
|
||
|
process = (VMProcess)workList.removeFirst();
|
||
|
}
|
||
|
|
||
|
if (process != null)
|
||
|
spawn(process);
|
||
|
|
||
|
|
||
|
// Check for termination of active child processes
|
||
|
while (!activeMap.isEmpty() && VMProcess.nativeReap())
|
||
|
{
|
||
|
long pid = VMProcess.reapedPid;
|
||
|
int exitValue = VMProcess.reapedExitValue;
|
||
|
process = (VMProcess)activeMap.remove(new Long(pid));
|
||
|
if (process != null)
|
||
|
{
|
||
|
synchronized (process)
|
||
|
{
|
||
|
process.exitValue = exitValue;
|
||
|
process.state = TERMINATED;
|
||
|
process.notify();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
System.err.println("VMProcess WARNING reaped unknown process: "
|
||
|
+ pid);
|
||
|
}
|
||
|
|
||
|
|
||
|
// If there are more new processes to create, go do that now.
|
||
|
// If there is nothing left to do, exit this thread. Otherwise,
|
||
|
// sleep a little while, and then check again for reapable children.
|
||
|
// We will get woken up immediately if there are new processes to
|
||
|
// spawn, but not if there are new children to reap. So we only
|
||
|
// sleep a short time, in effect polling while processes are active.
|
||
|
synchronized (workList)
|
||
|
{
|
||
|
if (!workList.isEmpty())
|
||
|
continue;
|
||
|
if (activeMap.isEmpty())
|
||
|
{
|
||
|
processThread = null;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
workList.wait(MAX_REAP_DELAY);
|
||
|
}
|
||
|
catch (InterruptedException e)
|
||
|
{
|
||
|
/* ignore */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Spawn a process
|
||
|
private void spawn(VMProcess process)
|
||
|
{
|
||
|
|
||
|
// Spawn the process and put it in our active map indexed by pid.
|
||
|
// If the spawn operation fails, store the exception with the process.
|
||
|
// In either case, wake up thread that created the process.
|
||
|
synchronized (process)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
process.nativeSpawn(process.cmd, process.env, process.dir);
|
||
|
process.state = RUNNING;
|
||
|
activeMap.put(new Long(process.pid), process);
|
||
|
}
|
||
|
catch (ThreadDeath death)
|
||
|
{
|
||
|
throw death;
|
||
|
}
|
||
|
catch (Throwable t)
|
||
|
{
|
||
|
process.state = TERMINATED;
|
||
|
process.exception = t;
|
||
|
}
|
||
|
process.notify();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Constructor
|
||
|
private VMProcess(String[] cmd, String[] env, File dir) throws IOException
|
||
|
{
|
||
|
|
||
|
// Initialize this process
|
||
|
this.state = INITIAL;
|
||
|
this.cmd = cmd;
|
||
|
this.env = env;
|
||
|
this.dir = dir;
|
||
|
|
||
|
// Add process to the new process work list and wakeup processThread
|
||
|
synchronized (workList)
|
||
|
{
|
||
|
workList.add(this);
|
||
|
if (processThread == null)
|
||
|
{
|
||
|
processThread = new ProcessThread();
|
||
|
processThread.setDaemon(true);
|
||
|
processThread.start();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
workList.notify();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Wait for processThread to spawn this process and update its state
|
||
|
synchronized (this)
|
||
|
{
|
||
|
while (state == INITIAL)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
wait();
|
||
|
}
|
||
|
catch (InterruptedException e)
|
||
|
{
|
||
|
/* ignore */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If spawning failed, rethrow the exception in this thread
|
||
|
if (exception != null)
|
||
|
{
|
||
|
exception.fillInStackTrace();
|
||
|
if (exception instanceof IOException)
|
||
|
throw (IOException)exception;
|
||
|
|
||
|
if (exception instanceof Error)
|
||
|
throw (Error)exception;
|
||
|
|
||
|
if (exception instanceof RuntimeException)
|
||
|
throw (RuntimeException)exception;
|
||
|
|
||
|
throw new RuntimeException(exception);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Invoked by native code (from nativeSpawn()) to record process info.
|
||
|
private void setProcessInfo(OutputStream stdin,
|
||
|
InputStream stdout, InputStream stderr, long pid)
|
||
|
{
|
||
|
this.stdin = stdin;
|
||
|
this.stdout = stdout;
|
||
|
this.stderr = stderr;
|
||
|
this.pid = pid;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Entry point from Runtime.exec().
|
||
|
*/
|
||
|
static Process exec(String[] cmd, String[] env, File dir) throws IOException
|
||
|
{
|
||
|
return new VMProcess(cmd, env, dir);
|
||
|
}
|
||
|
|
||
|
public OutputStream getOutputStream()
|
||
|
{
|
||
|
return stdin;
|
||
|
}
|
||
|
|
||
|
public InputStream getInputStream()
|
||
|
{
|
||
|
return stdout;
|
||
|
}
|
||
|
|
||
|
public InputStream getErrorStream()
|
||
|
{
|
||
|
return stderr;
|
||
|
}
|
||
|
|
||
|
public synchronized int waitFor() throws InterruptedException
|
||
|
{
|
||
|
while (state != TERMINATED)
|
||
|
wait();
|
||
|
return exitValue;
|
||
|
}
|
||
|
|
||
|
public synchronized int exitValue()
|
||
|
{
|
||
|
if (state != TERMINATED)
|
||
|
throw new IllegalThreadStateException();
|
||
|
return exitValue;
|
||
|
}
|
||
|
|
||
|
public synchronized void destroy()
|
||
|
{
|
||
|
if (state == TERMINATED)
|
||
|
return;
|
||
|
|
||
|
nativeKill(pid);
|
||
|
|
||
|
while (state != TERMINATED)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
wait();
|
||
|
}
|
||
|
catch (InterruptedException e)
|
||
|
{
|
||
|
/* ignore */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Does the fork()/exec() thing to create the O/S process.
|
||
|
* Must invoke setProcessInfo() before returning successfully.
|
||
|
* This method is only invoked by processThread.
|
||
|
*
|
||
|
* @throws IOException if the O/S process could not be created.
|
||
|
*/
|
||
|
native void nativeSpawn(String[] cmd, String[] env, File dir)
|
||
|
throws IOException;
|
||
|
|
||
|
/**
|
||
|
* Test for a reapable child process, and reap if so. Does not block.
|
||
|
* If a child was reaped, this method must set reapedPid and
|
||
|
* reapedExitValue appropriately before returning.
|
||
|
* This method is only invoked by processThread.
|
||
|
*
|
||
|
* @return true if a child was reaped, otherwise false
|
||
|
*/
|
||
|
// This is not private as it is called from an inner class.
|
||
|
static native boolean nativeReap();
|
||
|
|
||
|
/**
|
||
|
* Kill a process. This sends it a fatal signal but does not reap it.
|
||
|
*/
|
||
|
private static native void nativeKill(long pid);
|
||
|
}
|