/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Common Public License (CPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/cpl1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.scheduler.greenthreads; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.jikesrvm.VM; import org.vmmagic.pragma.Uninterruptible; /** * Jikes RVM implementation of <code>java.lang.Process</code>. */ public class VM_Process extends java.lang.Process { static { System.loadLibrary("rvmexec"); } // VM_Processor from which the child process was created. // Linux forces us to use the same pthread when we wait // for the child process to exit. VM_GreenProcessor creatingProcessor; // Child's Unix process id private int pid; // File descriptors and streams connecting VM to child process. private int inputDescriptor; private int outputDescriptor; private int errorDescriptor; private OutputStream outputStream; // connected to process's stdin private InputStream inputStream; // connected to process's stdout private InputStream errorStream; // connected to process's stderr // Data members for getting the process's exit status. // exitStatusLock must be held to access or modify. private Object exitStatusLock = new Object(); private boolean waiting = false; private boolean hasExited = false; private int exitStatus; /** * Constructor. * @param program name of program being executed * @param args array of program arguments * @param env array of environment variable bindings (optional) * @param dirPath name of directory to use as working directory (optional) */ public VM_Process(String program, String[] args, String[] env, String dirPath) { pid = exec4(program, args, env, dirPath); creatingProcessor = VM_GreenProcessor.getCurrentProcessor(); // FIXME: check for error creating process // initialize file descriptors VM_FileSystem.onCreateFileDescriptor(inputDescriptor, false); VM_FileSystem.onCreateFileDescriptor(outputDescriptor, false); VM_FileSystem.onCreateFileDescriptor(errorDescriptor, false); } /** * Get the <code>VM_Processor</code> that the child process was * created from. */ @Uninterruptible public VM_GreenProcessor getCreatingProcessor() { return creatingProcessor; } /** * Get the pid of this process. */ public int getPid() { return pid; } /** * Get the exit value of the process. * @throws IllegalThreadStateException if the process hasn't exited yet */ public int exitValue() { synchronized (exitStatusLock) { if (!hasExited) { // If someone else is waiting for the process to exit, // then it obviously hasn't exited. Otherwise, poll to see if // the process has exited. if (waiting || !isDead()) { throw new IllegalThreadStateException("I'm not dead yet!"); } // Process is dead, and we've recorded its exit status, // so let anyone waiting for its status know that // we've got it. hasExited = true; exitStatusLock.notifyAll(); } return exitStatus; } } /** * Wait for the process to exit. * @return the process's exit value */ public int waitFor() throws InterruptedException { synchronized (exitStatusLock) { // Make sure no other thread is waiting // for the process to exit. while (waiting) { exitStatusLock.wait(); } // Maybe the process has exited already? if (hasExited) { return exitStatus; } // We know that the process hasn't exited, and that no one // else is waiting for it. Set waiting = true, to // mark the fact that WE are now doing the wait. waiting = true; } // exitStatusLock is now not held, so other threads can find out // that we're waiting without themselves being blocked, // i.e., threads that are calling exitValue(). // Try to wait for the process to exit. // We may be interrupted. int code; try { code = waitForInternal(); } catch (InterruptedException e) { // We were interrupted, so end the wait synchronized (exitStatusLock) { waiting = false; exitStatusLock.notifyAll(); } throw e; } // Process has exited! Broadcast the exit status. synchronized (exitStatusLock) { waiting = false; hasExited = true; exitStatus = code; exitStatusLock.notifyAll(); } return code; } /** * Kill the process. */ public void destroy() { // We only attempt to kill the process if // we think it's still alive. synchronized (exitStatusLock) { if (!hasExited) { destroyInternal(); } } } /** * Get an <code>InputStream</code> to read process's stderr. */ public InputStream getErrorStream() { if (errorStream == null) errorStream = getInputStream(errorDescriptor); return errorStream; } /** * Get an <code>InputStream</code> to read process's stdout. */ public InputStream getInputStream() { if (inputStream == null) inputStream = getInputStream(outputDescriptor); return inputStream; } /** * Get an <code>OutputStream</code> to write process's stdin. */ public OutputStream getOutputStream() { if (outputStream == null) outputStream = getOutputStream(inputDescriptor); return outputStream; } /** * Poll to see if process is dead. * <code>exitStatusLock</code> must be held. */ private boolean isDead() { try { // Using a timeout of 1 second should give the // VM_Processor a couple chances to poll to see if the // process has exited. VM_ThreadProcessWaitData waitData = VM_Wait.processWait(this, 1.0d); boolean finished = waitData.finished; if (finished) { exitStatus = waitData.exitStatus; } return finished; } catch (InterruptedException e) { // The thread can get interrupted while // on the process wait queue. However, // this is not a blocking call, // so we don't want to actually throw an exception. java.lang.Thread.currentThread().interrupt(); return false; } } /** * Blocking wait to see if process is dead. * Returns exit status. Should be called with * <code>exitStatusLock</code> NOT held, but <code>waiting</code> * set to true so no other thread tries to wait * at the same time. (Unix semantics require us to * do only a single <code>waitpid()</code> for the same process). */ private int waitForInternal() throws InterruptedException { VM_ThreadProcessWaitData waitData = VM_Wait.processWait(this, VM_ThreadEventConstants.WAIT_INFINITE); // InterruptedException may be thrown, // in which case we definitely did NOT do the waitpid() if (VM.VerifyAssertions) VM._assert(waitData.finished); return waitData.exitStatus; } /** * Call fork() and execvp() to spawn the process. * @return the process id */ private native int exec4(String program, String[] args, String[] env, String dirPath); /** * Send the process a kill signal. */ private native void destroyInternal(); private InputStream getInputStream(final int fd) { return new InputStream() { public int available() throws IOException { return VM_FileSystem.bytesAvailable(fd); } public void close() throws IOException { // stream closed implicitly when process exits } public int read() throws IOException { return VM_FileSystem.readByte(fd); } public int read(byte[] buffer) throws IOException { return VM_FileSystem.readBytes(fd, buffer, 0, buffer.length); } public int read(byte[] buf, int off, int len) throws IOException { return VM_FileSystem.readBytes(fd, buf, off, len); } }; } private OutputStream getOutputStream(final int fd) { return new OutputStream() { public void write(int b) throws IOException { VM_FileSystem.writeByte(fd, b); } public void write(byte[] b) throws IOException { VM_FileSystem.writeBytes(fd, b, 0, b.length); } public void write(byte[] b, int off, int len) throws IOException { VM_FileSystem.writeBytes(fd, b, off, len); } public void flush() throws IOException { VM_FileSystem.sync(fd); } public void close() throws IOException { // stream closed implicitly when process exits } }; } }