commit-classpath
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: 0.09 release target date is next Friday April 30


From: Mark Wielaard
Subject: Re: 0.09 release target date is next Friday April 30
Date: Fri, 23 Apr 2004 00:13:42 +0200

Hi (CC commit-classpath so others can review),

On Thu, 2004-04-22 at 20:03, Archie Cobbs wrote:
> Mark Wielaard wrote:
> > I had some troubles with waitpid which was using an uninitialized pid_t
> > argument which I assume should actually be -1. And FileDescriptors are
> > now created differently. I'll let you know when I have it up and
> > running.
> 
> Great.. here's the latest version of my patch. It probably has some
> of the same fixes you have made, plus a few others.. hopefully our
> fixes should merge easily.

OK. I added two things.

- Explicitly set the path to change to.
- Make setProcessInfo() accept Input and Output streams which are now
  completely constructed natively (because of changes to
  FileDescriptor).

My diff is attached.

There is only one test in Mauve for Runtime.exec() though (and it isn't
really a test for Runtime.exec()) so it probably needs some more
testing. But it is certainly better then what we had!

We do need to reindent the java sources to conform to the coding
conventions. And I would like to see the VMProcess interface being a bit
smaller. Maybe we can have a package private class non-abstract
ClasspathProcess class in java/lang. And then a VMProcess class that
just has the nativeSpawn, nativeReap and nativeKill methods and holds
the pid. That would prevent to much copy/paste programming for some of
the runtime implementers. But I haven't thought about that very hard
yet.

Thanks,

Mark
Index: include/Makefile.am
===================================================================
RCS file: /cvsroot/classpath/classpath/include/Makefile.am,v
retrieving revision 1.15
diff -u -u -r1.15 Makefile.am
--- include/Makefile.am 18 Apr 2004 15:41:17 -0000      1.15
+++ include/Makefile.am 22 Apr 2004 22:05:05 -0000
@@ -49,6 +49,7 @@
 $(top_srcdir)/include/java_lang_Math.h \
 $(top_srcdir)/include/java_lang_Object.h \
 $(top_srcdir)/include/java_lang_VMFloat.h \
+$(top_srcdir)/include/java_lang_VMProcess.h \
 $(top_srcdir)/include/java_lang_VMRuntime.h \
 $(top_srcdir)/include/java_lang_VMSystem.h \
 $(top_srcdir)/include/java_lang_reflect_Array.h \
@@ -151,6 +152,8 @@
        $(JAVAH) -o $@ java.lang.Object
 $(top_srcdir)/include/java_lang_VMFloat.h: 
$(top_srcdir)/vm/reference/java/lang/VMFloat.java
        $(JAVAH) -o $@ java.lang.VMFloat
+$(top_srcdir)/include/java_lang_VMProcess.h: 
$(top_srcdir)/vm/reference/java/lang/VMProcess.java
+       $(JAVAH) -o $@ java.lang.VMProcess
 $(top_srcdir)/include/java_lang_VMRuntime.h: 
$(top_srcdir)/vm/reference/java/lang/VMRuntime.java
        $(JAVAH) -o $@ java.lang.VMRuntime
 $(top_srcdir)/include/java_lang_VMSystem.h: 
$(top_srcdir)/vm/reference/java/lang/VMSystem.java
Index: include/java_lang_VMProcess.h
===================================================================
RCS file: include/java_lang_VMProcess.h
diff -N include/java_lang_VMProcess.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ include/java_lang_VMProcess.h       22 Apr 2004 22:05:05 -0000
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+
+#ifndef __java_lang_VMProcess__
+#define __java_lang_VMProcess__
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+extern JNIEXPORT void JNICALL Java_java_lang_VMProcess_nativeSpawn (JNIEnv 
*env, jobject, jobjectArray, jobjectArray, jobject);
+extern JNIEXPORT jboolean JNICALL Java_java_lang_VMProcess_nativeReap (JNIEnv 
*env, jclass);
+extern JNIEXPORT void JNICALL Java_java_lang_VMProcess_nativeKill (JNIEnv 
*env, jclass, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __java_lang_VMProcess__ */
Index: include/java_lang_VMRuntime.h
===================================================================
RCS file: /cvsroot/classpath/classpath/include/java_lang_VMRuntime.h,v
retrieving revision 1.2
diff -u -u -r1.2 java_lang_VMRuntime.h
--- include/java_lang_VMRuntime.h       27 Dec 2003 16:38:43 -0000      1.2
+++ include/java_lang_VMRuntime.h       22 Apr 2004 22:05:05 -0000
@@ -23,7 +23,6 @@
 extern JNIEXPORT void JNICALL Java_java_lang_VMRuntime_exit (JNIEnv *env, 
jclass, jint);
 extern JNIEXPORT jint JNICALL Java_java_lang_VMRuntime_nativeLoad (JNIEnv 
*env, jclass, jstring);
 extern JNIEXPORT jstring JNICALL Java_java_lang_VMRuntime_nativeGetLibname 
(JNIEnv *env, jclass, jstring, jstring);
-extern JNIEXPORT jobject JNICALL Java_java_lang_VMRuntime_exec (JNIEnv *env, 
jclass, jobjectArray, jobjectArray, jobject);
 extern JNIEXPORT void JNICALL Java_java_lang_VMRuntime_insertSystemProperties 
(JNIEnv *env, jclass, jobject);
 
 #ifdef __cplusplus
Index: native/jni/java-lang/Makefile.am
===================================================================
RCS file: /cvsroot/classpath/classpath/native/jni/java-lang/Makefile.am,v
retrieving revision 1.7
diff -u -u -r1.7 Makefile.am
--- native/jni/java-lang/Makefile.am    12 Apr 2004 10:43:41 -0000      1.7
+++ native/jni/java-lang/Makefile.am    22 Apr 2004 22:05:19 -0000
@@ -5,7 +5,8 @@
                        java_lang_VMFloat.c \
                        java_lang_Double.c \
                        java_lang_VMDouble.c \
-                       java_lang_Math.c
+                       java_lang_Math.c \
+                       java_lang_VMProcess.c
 
 libjavalangreflect_la_SOURCES = java_lang_reflect_Array.c
 
Index: native/jni/java-lang/java_lang_VMProcess.c
===================================================================
RCS file: native/jni/java-lang/java_lang_VMProcess.c
diff -N native/jni/java-lang/java_lang_VMProcess.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ native/jni/java-lang/java_lang_VMProcess.c  22 Apr 2004 22:05:19 -0000
@@ -0,0 +1,482 @@
+/* java_lang_VMProcess.c -- native code for java.lang.VMProcess
+   Copyright (C) 1998, 1999, 2000, 2002, 2004 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., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 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. */
+
+#include <config.h>
+
+#include "java_lang_VMProcess.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#include "target_native.h"
+#include "target_native_misc.h"
+
+/* Internal functions */
+static char *copy_string(JNIEnv *env, jobject string);
+static char *copy_elem(JNIEnv *env, jobject stringArray, jint i);
+
+/* Some O/S's don't declare this */
+extern char **environ;
+
+/*
+ * Internal helper function to copy a String in UTF-8 format.
+ */
+static char *
+copy_string(JNIEnv *env, jobject string)
+{
+  char errbuf[64];
+  const jbyte *utf;
+  jclass clazz;
+  char *copy;
+
+  /* Check for null */
+  if (string == NULL)
+    {
+      clazz = (*env)->FindClass(env, "java/lang/NullPointerException");
+      if ((*env)->ExceptionOccurred(env))
+       return NULL;
+      (*env)->ThrowNew(env, clazz, NULL);
+      return NULL;
+    }
+
+  /* Extract UTF-8 */
+  utf = (*env)->GetStringUTFChars(env, string, NULL);
+  if ((*env)->ExceptionOccurred(env))
+    return NULL;
+
+  /* Copy it */
+  if ((copy = strdup(utf)) == NULL)
+    {
+      TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf, sizeof(errbuf),
+       "strdup: %s", strerror(errno));
+      clazz = (*env)->FindClass(env, "java/lang/InternalError");
+      if ((*env)->ExceptionOccurred(env))
+       return NULL;
+      (*env)->ThrowNew(env, clazz, errbuf);
+    }
+
+  /* Done */
+  (*env)->ReleaseStringUTFChars(env, string, utf);
+  return copy;
+}
+
+/*
+ * Internal helper function to copy a String[] element in UTF-8 format.
+ */
+static char *
+copy_elem(JNIEnv *env, jobject stringArray, jint i)
+{
+  jobject elem;
+  char *rtn;
+
+  elem = (*env)->GetObjectArrayElement(env, stringArray, i);
+  if ((*env)->ExceptionOccurred(env))
+    return NULL;
+  if ((rtn = copy_string(env, elem)) == NULL)
+    return NULL;
+  (*env)->DeleteLocalRef(env, elem);
+  return rtn;
+}
+
+/*
+ * private final native void nativeSpawn(String[], String[], File)
+ *     throws java/io/IOException
+ */
+JNIEXPORT void JNICALL
+Java_java_lang_VMProcess_nativeSpawn(JNIEnv *env, jobject this,
+       jobjectArray cmdArray, jobjectArray envArray, jobject dirFile)
+{
+  int fds[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } };
+  jobject streams[3] = { NULL, NULL, NULL };
+  jobject dirString = NULL;
+  char **newEnviron = NULL;
+  jsize cmdArrayLen = 0;
+  jsize envArrayLen = 0;
+  char **strings = NULL;
+  int num_strings = 0;
+  char *dir = NULL;
+  pid_t pid = -1;
+  char errbuf[64];
+  jmethodID method;
+  jclass clazz;
+  int i;
+
+  /* Check for null */
+  if (cmdArray == NULL)
+    goto null_pointer_exception;
+
+  /* Invoke dirFile.getPath() */
+  if (dirFile != NULL)
+    {
+      clazz = (*env)->FindClass(env, "java/io/File");
+      if ((*env)->ExceptionOccurred(env))
+       return;
+      method = (*env)->GetMethodID(env,
+       clazz, "getPath", "()Ljava/lang/String;");
+      if ((*env)->ExceptionOccurred(env))
+       return;
+      dirString = (*env)->CallObjectMethod(env, dirFile, method);
+      if ((*env)->ExceptionOccurred(env))
+       return;
+      (*env)->DeleteLocalRef(env, clazz);
+    }
+
+  /*
+   * Allocate array of C strings. We put all the C strings we need to
+   * handle the command parameters, the new environment, and the new
+   * directory into a single array for simplicity of (de)allocation.
+   */
+  cmdArrayLen = (*env)->GetArrayLength(env, cmdArray);
+  if (envArray != NULL)
+    envArrayLen = (*env)->GetArrayLength(env, envArray);
+  if ((strings = malloc(((cmdArrayLen + 1)
+      + (envArray != NULL ? envArrayLen + 1 : 0)
+      + (dirString != NULL ? 1 : 0)) * sizeof(*strings))) == NULL)
+    {
+      TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf,
+       sizeof(errbuf), "malloc: %s", strerror(errno));
+      goto out_of_memory;
+    }
+
+  /* Extract C strings from the various String parameters */
+  for (i = 0; i < cmdArrayLen; i++)
+    {
+      if ((strings[num_strings++] = copy_elem(env, cmdArray, i)) == NULL)
+       goto done;
+    }
+  strings[num_strings++] = NULL;               /* terminate array with NULL */
+  if (envArray != NULL)
+    {
+      newEnviron = strings + num_strings;
+      for (i = 0; i < envArrayLen; i++)
+       {
+         if ((strings[num_strings++] = copy_elem(env, envArray, i)) == NULL)
+           goto done;
+       }
+      strings[num_strings++] = NULL;           /* terminate array with NULL */
+    }
+  if (dirString != NULL)
+    {
+      if ((dir = copy_string(env, dirString)) == NULL)
+       goto done;
+      strings[num_strings++] = dir;
+    }
+
+  /* Create inter-process pipes */
+  for (i = 0; i < 3; i++)
+    {
+      if (pipe(fds[i]) == -1)
+       {
+         TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf,
+           sizeof(errbuf), "pipe: %s", strerror(errno));
+         goto system_error;
+       }
+    }
+
+  /* Set close-on-exec flag for parent's ends of pipes */
+  (void)fcntl(fds[0][1], F_SETFD, 1);
+  (void)fcntl(fds[1][0], F_SETFD, 1);
+  (void)fcntl(fds[2][0], F_SETFD, 1);
+
+  /* Fork into parent and child processes */
+  if ((pid = fork()) == (pid_t)-1)
+    {
+      TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf,
+       sizeof(errbuf), "fork: %s", strerror(errno));
+      goto system_error;
+    }
+
+  /* Child becomes the new process */
+  if (pid == 0)
+    {
+      char *const path = strings[0];
+
+      i = 0;
+      while (strings[i] != NULL)
+        {
+         i++;
+       }
+
+      /* Move file descriptors to standard locations */
+      if (fds[0][0] != 0)
+       {
+         if (dup2(fds[0][0], 0) == -1)
+           {
+             fprintf(stderr, "dup2: %s", strerror(errno));
+             exit(127);
+           }
+         close(fds[0][0]);
+       }
+      if (fds[1][1] != 1)
+       {
+         if (dup2(fds[1][1], 1) == -1)
+           {
+             fprintf(stderr, "dup2: %s", strerror(errno));
+             exit(127);
+           }
+         close(fds[1][1]);
+       }
+      if (fds[2][1] != 2)
+       {
+         if (dup2(fds[2][1], 2) == -1)
+           {
+             fprintf(stderr, "dup2: %s", strerror(errno));
+             exit(127);
+           }
+         close(fds[2][1]);
+       }
+
+      /* Change into destination directory */
+      if (dir != NULL && chdir(dir) == -1)
+       {
+         fprintf(stderr, "%s: %s", dir, strerror(errno));
+         exit(127);
+       }
+
+      /* Make argv[0] last component of executable pathname */
+      /* XXX should use "file.separator" property here XXX */
+      for (i = strlen(path); i > 0 && path[i - 1] != '/'; i--);
+      strings[0] = path + i;
+
+      /* Set new environment */
+      if (newEnviron != NULL)
+       environ = newEnviron;
+
+      /* Execute new program (this will close the parent end of the pipes) */
+      execvp(path, strings);
+
+      /* Failed */
+      fprintf(stderr, "%s: %s", path, strerror(errno));
+      exit(127);
+    }
+
+  /* Create Input/OutputStream objects around parent file descriptors */
+  clazz = (*env)->FindClass(env, "gnu/java/nio/channels/FileChannelImpl");
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  method = (*env)->GetMethodID(env, clazz, "<init>", "(II)V");
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  for (i = 0; i < 3; i++)
+    {
+      /* Mode is WRITE (2) for in and READ (1) for out and err. */
+      const int fd = fds[i][i == 0];
+      const int mode = (i == 0) ? 2 : 1;
+      jclass sclazz;
+      jmethodID smethod;
+
+      jobject channel = (*env)->NewObject(env, clazz, method, fd, mode);
+      if ((*env)->ExceptionOccurred(env))
+       goto done;
+
+      if (mode == 2)
+       sclazz = (*env)->FindClass(env, "java/io/FileOutputStream");
+      else
+       sclazz = (*env)->FindClass(env, "java/io/FileInputStream");
+      if ((*env)->ExceptionOccurred(env))
+       goto done;
+
+      smethod = (*env)->GetMethodID(env, sclazz, "<init>",
+                       "(Lgnu/java/nio/channels/FileChannelImpl;)V");
+      if ((*env)->ExceptionOccurred(env))
+       goto done;
+
+      streams[i] = (*env)->NewObject(env, sclazz, smethod, channel);
+      if ((*env)->ExceptionOccurred(env))
+       goto done;
+
+      (*env)->DeleteLocalRef(env, sclazz);
+      (*env)->DeleteLocalRef(env, smethod);
+    }
+  (*env)->DeleteLocalRef(env, clazz);
+
+  /* Invoke VMProcess.setProcessInfo() to update VMProcess object */
+  method = (*env)->GetMethodID(env,
+    (*env)->GetObjectClass(env, this), "setProcessInfo",
+    "(Ljava/io/OutputStream;Ljava/io/InputStream;Ljava/io/InputStream;J)V");
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  (*env)->CallVoidMethod(env, this, method,
+    streams[0], streams[1], streams[2], (jlong)pid);
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  (*env)->DeleteLocalRef(env, clazz);
+
+done:
+  /*
+   * We get here in both the success and failure cases in the
+   * parent process. Our goal is to clean up the mess we created.
+   */
+
+  /* Close child's ends of pipes */
+  for (i = 0; i < 3; i++)
+    {
+      const int fd = fds[i][i != 0];
+
+      if (fd != -1)
+       close(fd);
+    }
+
+  /*
+   * Close parent's ends of pipes if Input/OutputStreams never got created.
+   * This can only happen in a failure case. If a Stream object
+   * was created for a file descriptor, we don't close it because it
+   * will get closed when the FileDescriptor object is finalized.
+   */
+  for (i = 0; i < 3; i++)
+    {
+      const int fd = fds[i][i == 0];
+
+      if (fd != -1 && streams[i] == NULL)
+       close(fd);
+    }
+
+  /* Free C strings */
+  while (num_strings > 0)
+    free(strings[--num_strings]);
+  free(strings);
+
+  /* Done */
+  return;
+
+null_pointer_exception:
+  clazz = (*env)->FindClass(env, "java/lang/NullPointerException");
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  (*env)->ThrowNew(env, clazz, NULL);
+  (*env)->DeleteLocalRef(env, clazz);
+  goto done;
+
+out_of_memory:
+  clazz = (*env)->FindClass(env, "java/lang/InternalError");
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  (*env)->ThrowNew(env, clazz, errbuf);
+  (*env)->DeleteLocalRef(env, clazz);
+  goto done;
+
+system_error:
+  clazz = (*env)->FindClass(env, "java/io/IOException");
+  if ((*env)->ExceptionOccurred(env))
+    goto done;
+  (*env)->ThrowNew(env, clazz, errbuf);
+  (*env)->DeleteLocalRef(env, clazz);
+  goto done;
+}
+
+/*
+ * private static final native boolean nativeReap()
+ */
+JNIEXPORT jboolean JNICALL
+Java_java_lang_VMProcess_nativeReap(JNIEnv *env, jclass clazz)
+{
+  char ebuf[64];
+  jfieldID field;
+  int status;
+  pid_t pid;
+
+  /* Try to reap a child process, but don't block */
+  if ((pid = waitpid((pid_t)-1, &status, WNOHANG)) == 0)
+    return JNI_FALSE;
+
+  /* Check result from waitpid() */
+  if (pid == (pid_t)-1)
+    {
+      if (errno == ECHILD)
+       return JNI_FALSE;
+      TARGET_NATIVE_MISC_FORMAT_STRING2(ebuf,
+       sizeof(ebuf), "waitpid(%ld): %s", (long)pid, strerror(errno));
+      clazz = (*env)->FindClass(env, "java/lang/InternalError");
+      if ((*env)->ExceptionOccurred(env))
+       return JNI_FALSE;
+      (*env)->ThrowNew(env, clazz, ebuf);
+      (*env)->DeleteLocalRef(env, clazz);
+      return JNI_FALSE;
+    }
+
+  /* Get exit code; for signal termination return negative signal value XXX */
+  if (WIFEXITED(status))
+    status = (jint)WEXITSTATUS(status);
+  else if (WIFSIGNALED(status))
+    status = -(jint)WTERMSIG(status);
+  else
+    return JNI_FALSE;                  /* process merely stopped; ignore */
+
+  /* Return process pid and exit status */
+  field = (*env)->GetStaticFieldID(env, clazz, "reapedPid", "J");
+  if ((*env)->ExceptionOccurred(env))
+    return JNI_FALSE;
+  (*env)->SetStaticLongField(env, clazz, field, (jlong)pid);
+  if ((*env)->ExceptionOccurred(env))
+    return JNI_FALSE;
+  field = (*env)->GetStaticFieldID(env, clazz, "reapedExitValue", "I");
+  if ((*env)->ExceptionOccurred(env))
+    return JNI_FALSE;
+  (*env)->SetStaticIntField(env, clazz, field, (jint)status);
+  if ((*env)->ExceptionOccurred(env))
+    return JNI_FALSE;
+
+  /* Done */
+  return JNI_TRUE;
+}
+
+/*
+ * private static final native void nativeKill(long)
+ */
+JNIEXPORT void JNICALL
+Java_java_lang_VMProcess_nativeKill(JNIEnv *env, jclass clazz, jlong pid)
+{
+  char ebuf[64];
+
+  if (kill((pid_t)pid, SIGKILL) == -1)
+    {
+      TARGET_NATIVE_MISC_FORMAT_STRING2(ebuf,
+       sizeof(ebuf), "kill(%ld): %s", (long)pid, strerror(errno));
+      clazz = (*env)->FindClass(env, "java/lang/InternalError");
+      if ((*env)->ExceptionOccurred(env))
+       return;
+      (*env)->ThrowNew(env, clazz, ebuf);
+      (*env)->DeleteLocalRef(env, clazz);
+    }
+}
+
Index: vm/reference/java/lang/Makefile.am
===================================================================
RCS file: /cvsroot/classpath/classpath/vm/reference/java/lang/Makefile.am,v
retrieving revision 1.8
diff -u -u -r1.8 Makefile.am
--- vm/reference/java/lang/Makefile.am  27 Dec 2003 16:38:43 -0000      1.8
+++ vm/reference/java/lang/Makefile.am  22 Apr 2004 22:05:20 -0000
@@ -8,6 +8,7 @@
 VMDouble.java \
 VMFloat.java \
 VMObject.java \
+VMProcess.java \
 VMRuntime.java \
 VMSecurityManager.java \
 VMString.java \
Index: vm/reference/java/lang/VMProcess.java
===================================================================
RCS file: vm/reference/java/lang/VMProcess.java
diff -N vm/reference/java/lang/VMProcess.java
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ vm/reference/java/lang/VMProcess.java       22 Apr 2004 22:05:20 -0000
@@ -0,0 +1,301 @@
+/* java.lang.VMProcess -- VM implementation of java.lang.Process
+   Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 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.FileInputStream;
+import java.io.FileOutputStream;
+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 address@hidden 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 address@hidden 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.
+  private static Thread processThread;
+
+  // New processes waiting to be spawned by processThread.
+  private 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.
+  private static long reapedPid;
+  private static int reapedExitValue;
+
+  // Information about this process
+  private int state;                           // current state of process
+  private final String[] cmd;                  // copied from Runtime.exec()
+  private final String[] env;                  // copied from Runtime.exec()
+  private final File dir;                      // copied from Runtime.exec()
+  private Throwable exception;                 // if process failed to start
+  private long pid;                            // process id
+  private OutputStream stdin;                  // process input stream
+  private InputStream stdout;                  // process output stream
+  private InputStream stderr;                  // process error stream
+  private 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
+    private final HashMap activeMap = new HashMap();
+
+    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 (VMProcess.nativeReap()) {
+         long pid = VMProcess.reapedPid;
+         int exitValue = VMProcess.reapedExitValue;
+         process = (VMProcess)activeMap.remove(new Long(pid));
+         synchronized (process) {
+           process.exitValue = exitValue;
+           process.state = TERMINATED;
+           process.notify();
+         }
+       }
+
+       // 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) {
+         }
+       }
+      }
+    }
+
+    // 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 (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) {
+       }
+      }
+    }
+
+    // If spawning failed, rethrow the exception in this thread
+    if (exception != null) {
+      exception.printStackTrace();
+      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 (true) {
+      try {
+       waitFor();
+      } catch (InterruptedException e) {
+      }
+    }
+  }
+
+  /**
+   * 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.
+   */
+  private 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
+   */
+  private 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);
+}
+
Index: vm/reference/java/lang/VMRuntime.java
===================================================================
RCS file: /cvsroot/classpath/classpath/vm/reference/java/lang/VMRuntime.java,v
retrieving revision 1.3
diff -u -u -r1.3 VMRuntime.java
--- vm/reference/java/lang/VMRuntime.java       27 Dec 2003 10:48:48 -0000      
1.3
+++ vm/reference/java/lang/VMRuntime.java       22 Apr 2004 22:05:20 -0000
@@ -38,6 +38,7 @@
 package java.lang;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Properties;
 
 /**
@@ -178,7 +179,10 @@
      * @return the newly created process
      * @throws NullPointerException if cmd or env have null elements
      */
-    static native Process exec(String[] cmd, String[] env, File dir);
+    static Process exec(String[] cmd, String[] env, File dir)
+       throws IOException {
+      return VMProcess.exec(cmd, env, dir);
+    }
 
     /**
      * Get the system properties. This is done here, instead of in System,

Attachment: signature.asc
Description: This is a digitally signed message part


reply via email to

[Prev in Thread] Current Thread [Next in Thread]