This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFA] Patch: Fix PosixProcess / PR libgcj 11801


This is the second version of the patch posted here:

http://gcc.gnu.org/ml/java-patches/2004-q3/msg00378.html

The main aim of the patch is two fold:

1) Don't leave zombie processes around.
2) Allow waitFor() from a different thread than that which started the
Process.


I made all of the changes suggested by Bryce McKinlay except for
converting all Process implementations (Posix, Win32, Ecos...) to use
their real names instead of ConcreteProcess and modifying the
configure/build process to match.


Tested on both i686-pc-linux-gnu (Fedora Core 1, NPTL) and
mipsel-linux-gnu (glibc 2.2.5) with no regressions, with additional
tests   from here:

http://gcc.gnu.org/ml/java-patches/2004-q3/msg00451.html


OK to commit?

David Daney.
2004-08-03  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 (_Jv_ThreadRegister) Block SIGCHLD.
	(_Jv_ThreadStart) Likewise.
	* configure.in (PLATFORM_INNER_NAT_HDRS): New AC_SUBST() used in...
	* Makefile.am: ... to specify extra native headers.
	* configure: Regenerated.
	* Makefile.in: Regenerated.
	* gcj/Makefile.in: Regenerated.
	* include/Makefile.in: Regenerated.
	* testsuite/Makefile.in: Regenerated.

Index: Makefile.am
===================================================================
RCS file: /cvs/gcc/gcc/libjava/Makefile.am,v
retrieving revision 1.396
diff -c -p -r1.396 Makefile.am
*** Makefile.am	24 Jul 2004 16:43:44 -0000	1.396
--- Makefile.am	3 Aug 2004 15:53:17 -0000
*************** inner_nat_headers = java/io/ObjectOutput
*** 531,537 ****
  	gnu/java/net/PlainSocketImpl$$SocketInputStream.h \
  	gnu/java/net/PlainSocketImpl$$SocketOutputStream.h \
  	gnu/java/nio/PipeImpl$$SinkChannelImpl.h \
! 	gnu/java/nio/PipeImpl$$SourceChannelImpl.h
  
  nat_headers = $(ordinary_nat_headers) $(inner_nat_headers)
  nat_headers_install = $(ordinary_nat_headers)
--- 531,538 ----
  	gnu/java/net/PlainSocketImpl$$SocketInputStream.h \
  	gnu/java/net/PlainSocketImpl$$SocketOutputStream.h \
  	gnu/java/nio/PipeImpl$$SinkChannelImpl.h \
! 	gnu/java/nio/PipeImpl$$SourceChannelImpl.h \
! 	$(PLATFORM_INNER_NAT_HDRS)
  
  nat_headers = $(ordinary_nat_headers) $(inner_nat_headers)
  nat_headers_install = $(ordinary_nat_headers)
*************** gnu/java/nio/PipeImpl$$SourceChannelImpl
*** 641,646 ****
--- 642,652 ----
  	$(GCJH) -classpath '' -bootclasspath $(top_builddir) \
  	'gnu/java/nio/PipeImpl$$SourceChannelImpl'
  
+ ## Only used by PosixProcess.java
+ java/lang/ConcreteProcess$$ProcessManager.h: java/lang/ConcreteProcess.class
+ 	$(GCJH) -classpath '' -bootclasspath $(top_builddir) \
+ 	'java/lang/ConcreteProcess$$ProcessManager'
+ 
  ## Headers we maintain by hand and which we want to install.
  extra_headers = java/lang/Object.h java/lang/Class.h
  
Index: configure.in
===================================================================
RCS file: /cvs/gcc/gcc/libjava/configure.in,v
retrieving revision 1.197
diff -c -p -r1.197 configure.in
*** configure.in	28 Jul 2004 19:08:44 -0000	1.197
--- configure.in	3 Aug 2004 15:53:21 -0000
*************** TARGET_ECOS="$with_ecos"
*** 403,408 ****
--- 403,411 ----
  EXTRA_CC_FILES=
  AC_SUBST(EXTRA_CC_FILES)
  
+ PLATFORM_INNER_NAT_HDRS=
+ AC_SUBST(PLATFORM_INNER_NAT_HDRS)
+ 
  PLATFORMOBJS=
  case "$TARGET_ECOS" in
     no) case "$host" in
*************** case "$TARGET_ECOS" in
*** 418,423 ****
--- 421,427 ----
  	    PLATFORMNET=Posix
              PLATFORMOBJS=posix.lo
  	    PLATFORMH=posix.h
+ 	    PLATFORM_INNER_NAT_HDRS='java/lang/ConcreteProcess$$ProcessManager.h'
        ;;
        esac
        ;;
Index: posix-threads.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/posix-threads.cc,v
retrieving revision 1.34
diff -c -p -r1.34 posix-threads.cc
*** posix-threads.cc	21 Oct 2003 04:46:19 -0000	1.34
--- posix-threads.cc	3 Aug 2004 15:53:21 -0000
***************
*** 1,6 ****
  // posix-threads.cc - interface between libjava and POSIX threads.
  
! /* Copyright (C) 1998, 1999, 2000, 2001  Free Software Foundation
  
     This file is part of libgcj.
  
--- 1,6 ----
  // posix-threads.cc - interface between libjava and POSIX threads.
  
! /* Copyright (C) 1998, 1999, 2000, 2001, 2004  Free Software Foundation
  
     This file is part of libgcj.
  
*************** details.  */
*** 34,39 ****
--- 34,40 ----
  #include <java/lang/System.h>
  #include <java/lang/Long.h>
  #include <java/lang/OutOfMemoryError.h>
+ #include <java/lang/InternalError.h>
  
  // This is used to implement thread startup.
  struct starter
*************** _Jv_ThreadRegister (_Jv_Thread_t *data)
*** 358,363 ****
--- 359,371 ----
  	_Jv_self_cache[current_index].high_sp_bits = BAD_HIGH_SP_VALUE;
        }
  # endif
+   // Block SIGCHLD which is used in natPosixProcess.cc.
+   sigset_t mask;
+   sigemptyset (&mask);
+   sigaddset (&mask, SIGCHLD);
+   int c = pthread_sigmask (SIG_BLOCK, &mask, NULL);
+   if (c != 0)
+     throw new java::lang::InternalError (JvNewStringLatin1 (strerror (c)));
  }
  
  void
*************** _Jv_ThreadStart (java::lang::Thread *thr
*** 403,408 ****
--- 411,425 ----
      return;
    data->flags |= FLAG_START;
  
+   // Block SIGCHLD which is used in natPosixProcess.cc.
+   // The current mask is inherited by the child thread.
+   sigset_t mask;
+   sigemptyset (&mask);
+   sigaddset (&mask, SIGCHLD);
+   int c = pthread_sigmask (SIG_BLOCK, &mask, NULL);
+   if (c != 0)
+     throw new java::lang::InternalError (JvNewStringLatin1 (strerror (c)));
+ 
    param.sched_priority = thread->getPriority();
  
    pthread_attr_init (&attr);
Index: gcj/javaprims.h
===================================================================
RCS file: /cvs/gcc/gcc/libjava/gcj/javaprims.h,v
retrieving revision 1.50
diff -c -p -r1.50 javaprims.h
*** gcj/javaprims.h	10 Jul 2004 14:50:15 -0000	1.50
--- gcj/javaprims.h	3 Aug 2004 15:53:21 -0000
***************
*** 1,6 ****
  // javaprims.h - Main external header file for libgcj.  -*- c++ -*-
  
! /* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003  Free Software Foundation
  
     This file is part of libgcj.
  
--- 1,7 ----
  // javaprims.h - Main external header file for libgcj.  -*- c++ -*-
  
! /* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004
!    Free Software Foundation
  
     This file is part of libgcj.
  
*************** extern "Java"
*** 150,155 ****
--- 151,157 ----
        class Comparable;
        class Compiler;
        class ConcreteProcess;
+       class ConcreteProcess$ProcessManager;
        class Double;
        class Error;
        class Exception;
Index: java/lang/PosixProcess.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/lang/PosixProcess.java,v
retrieving revision 1.5
diff -c -p -r1.5 PosixProcess.java
*** java/lang/PosixProcess.java	14 Aug 2002 01:07:59 -0000	1.5
--- java/lang/PosixProcess.java	3 Aug 2004 15:53:22 -0000
***************
*** 1,6 ****
  // PosixProcess.java - Subclass of Process for POSIX systems.
! 
! /* Copyright (C) 1998, 1999  Free Software Foundation
  
     This file is part of libgcj.
  
--- 1,5 ----
  // PosixProcess.java - Subclass of Process for POSIX systems.
! /* Copyright (C) 1998, 1999, 2004  Free Software Foundation
  
     This file is part of libgcj.
  
*************** details.  */
*** 11,86 ****
  package java.lang;
  
  import java.io.File;
  import java.io.InputStream;
  import java.io.OutputStream;
! import java.io.IOException;
  
  /**
   * @author Tom Tromey <tromey@cygnus.com>
   * @date May 3, 1999
   */
  
  // 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 ()
    {
!     if (! hasExited)
!       throw new IllegalThreadStateException("Process has not exited");
      return status;
    }
  
!   public InputStream getErrorStream ()
    {
      return errorStream;
    }
  
!   public InputStream getInputStream ()
    {
      return inputStream;
    }
  
!   public OutputStream getOutputStream ()
    {
      return outputStream;
    }
  
!   public native int waitFor () throws InterruptedException;
  
!   // 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;
  
    // 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
    {
!     startProcess (progarray, envp, dir);
    }
  
!   // 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;
  
!   // The exit status, if the child has exited.
!   private int status;
  
    // The streams.
    private InputStream errorStream;
    private InputStream inputStream;
    private OutputStream outputStream;
  }
--- 10,393 ----
  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;
    }
  
!   public InputStream getErrorStream()
    {
      return errorStream;
    }
  
!   public InputStream getInputStream()
    {
      return inputStream;
    }
  
!   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;
+ 
+   /**
+    * Lock Object for all processManager related locking.
+    */
+   private static Object queueLock = new Object();
+   private static ProcessManager processManager;
  }
Index: java/lang/Runtime.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/lang/Runtime.java,v
retrieving revision 1.15
diff -c -p -r1.15 Runtime.java
*** java/lang/Runtime.java	27 Jul 2004 18:42:37 -0000	1.15
--- java/lang/Runtime.java	3 Aug 2004 15:53:22 -0000
***************
*** 1,5 ****
  /* Runtime.java -- access to the VM process
!    Copyright (C) 1998, 2002, 2003 Free Software Foundation
  
  This file is part of GNU Classpath.
  
--- 1,5 ----
  /* Runtime.java -- access to the VM process
!    Copyright (C) 1998, 2002, 2003, 2004 Free Software Foundation
  
  This file is part of GNU Classpath.
  
*************** public class Runtime
*** 734,741 ****
     * @param dir the directory to use, may be null
     * @return the newly created process
     * @throws NullPointerException if cmd or env have null elements
     */
!   native Process execInternal(String[] cmd, String[] env, File dir);
  
    /**
     * Get the system properties. This is done here, instead of in System,
--- 734,744 ----
     * @param dir the directory to use, may be null
     * @return the newly created process
     * @throws NullPointerException if cmd or env have null elements
+    * @throws IOException if the exec fails
     */
!   native Process execInternal(String[] cmd, String[] env, File dir)
!     throws IOException;
!     
  
    /**
     * Get the system properties. This is done here, instead of in System,
Index: java/lang/natPosixProcess.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/lang/natPosixProcess.cc,v
retrieving revision 1.18
diff -c -p -r1.18 natPosixProcess.cc
*** java/lang/natPosixProcess.cc	1 Mar 2004 21:33:27 -0000	1.18
--- java/lang/natPosixProcess.cc	3 Aug 2004 15:53:22 -0000
*************** details.  */
*** 21,32 ****
--- 21,36 ----
  #include <string.h>
  #include <stdlib.h>
  #include <stdio.h>
+ #include <unistd.h>
+ #include <pthread.h>
  
  #include <gcj/cni.h>
  #include <jvm.h>
  
+ #include <java/lang/ConcreteProcess$ProcessManager.h>
  #include <java/lang/ConcreteProcess.h>
  #include <java/lang/IllegalThreadStateException.h>
+ #include <java/lang/InternalError.h>
  #include <java/lang/InterruptedException.h>
  #include <java/lang/NullPointerException.h>
  #include <java/lang/Thread.h>
*************** details.  */
*** 40,91 ****
  
  using gnu::java::nio::channels::FileChannelImpl;
  
! extern char **environ;
! 
! void
! java::lang::ConcreteProcess::destroy (void)
! {
!   if (! hasExited)
!     {
!       // Really kill it.
!       kill ((pid_t) pid, SIGKILL);
!     }
! }
! 
! jint
! java::lang::ConcreteProcess::waitFor (void)
! {
!   if (! hasExited)
!     {
!       int wstat;
!       int r = waitpid ((pid_t) pid, &wstat, 0);
! 
!       if (r == -1)
!         {
! 	  if (java::lang::Thread::interrupted())
! 	    throw new InterruptedException (JvNewStringLatin1 (strerror
! 	      (errno)));
! 	}
!       else
! 	{
! 	  hasExited = true;
! 
! 	  if (WIFEXITED (wstat))
! 	    status = WEXITSTATUS (wstat);
! 	  else
! 	    status = -1;
! 	}
!     }
! 
!   return status;
! }
  
  static char *
  new_string (jstring string)
  {
    jsize s = _Jv_GetStringUTFLength (string);
!   char *buf = (char *) _Jv_Malloc (s + 1);
!   _Jv_GetStringUTFRegion (string, 0, string->length(), buf);
    buf[s] = '\0';
    return buf;
  }
--- 44,57 ----
  
  using gnu::java::nio::channels::FileChannelImpl;
  
! extern char ** environ;
  
  static char *
  new_string (jstring string)
  {
    jsize s = _Jv_GetStringUTFLength (string);
!   char * buf = (char *) _Jv_Malloc (s + 1);
!   _Jv_GetStringUTFRegion (string, 0, string->length (), buf);
    buf[s] = '\0';
    return buf;
  }
*************** myclose (int &fd)
*** 120,138 ****
    fd = -1;
  }
  
  void
! java::lang::ConcreteProcess::startProcess (jstringArray progarray,
! 					   jstringArray envp,
! 					   java::io::File *dir)
  {
!   using namespace java::io;
  
!   hasExited = false;
  
    // Initialize all locals here to make cleanup simpler.
!   char **args = NULL;
!   char **env = NULL;
!   char *path = NULL;
    int inp[2], outp[2], errp[2], msgp[2];
    inp[0] = -1;
    inp[1] = -1;
--- 86,223 ----
    fd = -1;
  }
  
+ // There has to be a signal handler in order to be able to
+ // sigwait() on SIGCHLD.  The information passed is ignored as it
+ // will be recovered by the waitpid() call.
+ static void
+ sigchld_handler (int)
+ {
+   // Ignore.
+ }
+ 
+ 
+ // Get ready to enter the main reaper thread loop.
  void
! java::lang::ConcreteProcess$ProcessManager::init ()
  {
!   using namespace java::lang;
!   // Remenber our PID so other threads can kill us.
!   reaperPID = (jlong) pthread_self ();
! 
!   // SIGCHLD is blocked in all threads in posix-threads.cc.
!   // Setup the SIGCHLD handler.
!   struct sigaction sa;
!   memset (&sa, 0, sizeof (sa));
! 
!   sa.sa_handler = sigchld_handler;
!   // We only want signals when the things exit.
!   sa.sa_flags = SA_NOCLDSTOP;
! 
!   if (-1 == sigaction (SIGCHLD, &sa, NULL))
!     goto error;
! 
!   // All OK.
!   return;
! 
! error:
!   throw new InternalError (JvNewStringLatin1 (strerror (errno)));
! }
! 
! void
! java::lang::ConcreteProcess$ProcessManager::waitForSignal ()
! {
!   using namespace java::lang;
! 
!   sigset_t mask;
!   // Wait for SIGCHLD
!   sigemptyset (&mask);
!   sigaddset (&mask, SIGCHLD);
! 
!   int sig;
!   int c = sigwait (&mask, &sig);
! 
!   if (c != 0)
!     goto error;
! 
!   // All OK.
!   return;
! 
! error:
!   throw new InternalError (JvNewStringLatin1 (strerror (c)));
! }
! 
! jboolean java::lang::ConcreteProcess$ProcessManager::reap ()
! {
!   using namespace java::lang;
! 
!   pid_t pid;
! 
!   for (;;)
!     {
!       // Get the return code from a dead child process.
!       int status;
!       pid = waitpid ((pid_t) - 1, &status, WNOHANG);
!       if (pid == -1)
! 	{
! 	  if (errno == ECHILD)
! 	    return false;
! 	  else
! 	    goto error;
! 	}
! 
!       if (pid == 0)
! 	return true;   // No children to wait for.
! 
!       // Look up the process in our pid map.
!       ConcreteProcess * process = removeProcessFromMap ((jlong) pid);
! 
!       if (process)
! 	{
! 	  JvSynchronize sync (process);
! 	  process->status = WIFEXITED (status) ? WEXITSTATUS (status) : -1;
! 	  process->state = ConcreteProcess::STATE_TERMINATED;
! 	  process->notifyAll ();
! 	}
!       else
! 	{
! 	  // Unknown child.  How did this happen?
! 	  fprintf (stderr, "Reaped unknown child pid = %ld\n", (long) pid);
! 	}
!     }
! 
! error:
!   throw new InternalError (JvNewStringLatin1 (strerror (errno)));
! }
! 
! void
! java::lang::ConcreteProcess$ProcessManager::signalReaper ()
! {
!   int c = pthread_kill ((pthread_t) reaperPID, SIGCHLD);
!   if (c == 0)
!     return;
!   // pthread_kill() failed.
!   throw new InternalError (JvNewStringLatin1 (strerror (c)));
! }
  
! void
! java::lang::ConcreteProcess::nativeDestroy ()
! {
!   int c = kill ((pid_t) pid, SIGKILL);
!   if (c == 0)
!     return;
!   // kill() failed.
!   throw new InternalError (JvNewStringLatin1 (strerror (errno)));
! }
! 
! void
! java::lang::ConcreteProcess::nativeSpawn ()
! {
!   using namespace java::io;
  
    // Initialize all locals here to make cleanup simpler.
!   char ** args = NULL;
!   char ** env = NULL;
!   char *  path = NULL;
    int inp[2], outp[2], errp[2], msgp[2];
    inp[0] = -1;
    inp[1] = -1;
*************** java::lang::ConcreteProcess::startProces
*** 142,338 ****
    errp[1] = -1;
    msgp[0] = -1;
    msgp[1] = -1;
-   java::lang::Throwable *exc = NULL;
    errorStream = NULL;
    inputStream = NULL;
    outputStream = NULL;
  
    try
!     {
!       // Transform arrays to native form.
!       args = (char **) _Jv_Malloc ((progarray->length + 1)
! 				   * sizeof (char *));
! 
!       // Initialize so we can gracefully recover.
!       jstring *elts = elements (progarray);
!       for (int i = 0; i <= progarray->length; ++i)
! 	args[i] = NULL;
! 
!       for (int i = 0; i < progarray->length; ++i)
! 	args[i] = new_string (elts[i]);
!       args[progarray->length] = NULL;
! 
!       if (envp)
! 	{
! 	  env = (char **) _Jv_Malloc ((envp->length + 1) * sizeof (char *));
! 	  elts = elements (envp);
! 
! 	  // Initialize so we can gracefully recover.
! 	  for (int i = 0; i <= envp->length; ++i)
! 	    env[i] = NULL;
! 
! 	  for (int i = 0; i < envp->length; ++i)
! 	    env[i] = new_string (elts[i]);
! 	  env[envp->length] = NULL;
! 	}
! 
!       // We allocate this here because we can't call malloc() after
!       // the fork.
!       if (dir != NULL)
! 	path = new_string (dir->getPath ());
! 
!       // Create pipes for I/O.  MSGP is for communicating exec()
!       // status.
!       if (pipe (inp) || pipe (outp) || pipe (errp) || pipe (msgp)
! 	  || fcntl (msgp[1], F_SETFD, FD_CLOEXEC))
! 	throw new IOException (JvNewStringLatin1 (strerror (errno)));
! 
!       // We create the streams before forking.  Otherwise if we had an
!       // error while creating the streams we would have run the child
!       // with no way to communicate with it.
!       errorStream = new FileInputStream (new FileChannelImpl(errp[0], FileChannelImpl::READ));
!       inputStream = new FileInputStream (new FileChannelImpl(inp[0], FileChannelImpl::READ));
!       outputStream = new FileOutputStream (new FileChannelImpl(outp[1], FileChannelImpl::WRITE));
! 
!       // We don't use vfork() because that would cause the local
!       // environment to be set by the child.
!       if ((pid = (jlong) fork ()) == -1)
! 	throw new IOException (JvNewStringLatin1 (strerror (errno)));
! 
!       if (pid == 0)
! 	{
! 	  // Child process, so remap descriptors, chdir and exec.
! 
! 	  if (envp)
! 	    {
! 	      // Preserve PATH and LD_LIBRARY_PATH unless specified
! 	      // explicitly.
! 	      char *path_val = getenv ("PATH");
! 	      char *ld_path_val = getenv ("LD_LIBRARY_PATH");
! 	      environ = env;
! 	      if (path_val && getenv ("PATH") == NULL)
! 		{
! 		  char *path_env = (char *) _Jv_Malloc (strlen (path_val)
! 							+ 5 + 1);
! 		  strcpy (path_env, "PATH=");
! 		  strcat (path_env, path_val);
! 		  putenv (path_env);
! 		}
! 	      if (ld_path_val && getenv ("LD_LIBRARY_PATH") == NULL)
! 		{
! 		  char *ld_path_env
! 		    = (char *) _Jv_Malloc (strlen (ld_path_val) + 16 + 1);
! 		  strcpy (ld_path_env, "LD_LIBRARY_PATH=");
! 		  strcat (ld_path_env, ld_path_val);
! 		  putenv (ld_path_env);
! 		}
! 	    }
! 
! 	  // We ignore errors from dup2 because they should never occur.
! 	  dup2 (outp[0], 0);
! 	  dup2 (inp[1], 1);
! 	  dup2 (errp[1], 2);
! 
! 	  // Use close and not myclose -- we're in the child, and we
! 	  // aren't worried about the possible race condition.
! 	  close (inp[0]);
! 	  close (inp[1]);
! 	  close (errp[0]);
! 	  close (errp[1]);
! 	  close (outp[0]);
! 	  close (outp[1]);
! 	  close (msgp[0]);
!           
! 	  // Change directory.
! 	  if (path != NULL)
! 	    {
! 	      if (chdir (path) != 0)
! 		{
! 		  char c = errno;
! 		  write (msgp[1], &c, 1);
! 		  _exit (127);
! 		}
! 	    }
! 
! 	  execvp (args[0], args);
! 
! 	  // Send the parent notification that the exec failed.
! 	  char c = errno;
! 	  write (msgp[1], &c, 1);
! 	  _exit (127);
! 	}
  
!       // Parent.  Close extra file descriptors and mark ours as
!       // close-on-exec.
!       myclose (outp[0]);
!       myclose (inp[1]);
!       myclose (errp[1]);
!       myclose (msgp[1]);
! 
!       char c;
!       int r = read (msgp[0], &c, 1);
!       if (r == -1)
! 	throw new IOException (JvNewStringLatin1 (strerror (errno)));
!       else if (r != 0)
! 	throw new IOException (JvNewStringLatin1 (strerror (c)));
      }
!   catch (java::lang::Throwable *thrown)
      {
!       // Do some cleanup we only do on failure.  If a stream object
!       // has been created, we must close the stream itself (to avoid
!       // duplicate closes when the stream object is collected).
!       // Otherwise we simply close the underlying file descriptor.
!       // We ignore errors here as they are uninteresting.
! 
!       try
! 	{
! 	  if (inputStream != NULL)
! 	    inputStream->close ();
! 	  else
! 	    myclose (inp[0]);
! 	}
!       catch (java::lang::Throwable *ignore)
! 	{
! 	}
  
!       try
! 	{
! 	  if (outputStream != NULL)
! 	    outputStream->close ();
! 	  else
! 	    myclose (outp[1]);
! 	}
!       catch (java::lang::Throwable *ignore)
! 	{
! 	}
  
!       try
! 	{
! 	  if (errorStream != NULL)
! 	    errorStream->close ();
! 	  else
! 	    myclose (errp[0]);
! 	}
!       catch (java::lang::Throwable *ignore)
! 	{
! 	}
  
!       // These are potentially duplicate, but it doesn't matter due to
!       // the use of myclose.
!       myclose (outp[0]);
!       myclose (inp[1]);
!       myclose (errp[1]);
!       myclose (msgp[1]);
  
!       exc = thrown;
!     }
  
    myclose (msgp[0]);
    cleanup (args, env, path);
  
!   if (exc != NULL)
!     throw exc;
!   else
      {
        fcntl (outp[1], F_SETFD, FD_CLOEXEC);
        fcntl (inp[0], F_SETFD, FD_CLOEXEC);
--- 227,429 ----
    errp[1] = -1;
    msgp[0] = -1;
    msgp[1] = -1;
    errorStream = NULL;
    inputStream = NULL;
    outputStream = NULL;
  
    try
!   {
!     // Transform arrays to native form.
!     args = (char **) _Jv_Malloc ((progarray->length + 1) * sizeof (char *));
! 
!     // Initialize so we can gracefully recover.
!     jstring * elts = elements (progarray);
!     for (int i = 0; i <= progarray->length; ++i)
!       args[i] = NULL;
! 
!     for (int i = 0; i < progarray->length; ++i)
!       args[i] = new_string (elts[i]);
!     args[progarray->length] = NULL;
! 
!     if (envp)
!       {
! 	env = (char **) _Jv_Malloc ((envp->length + 1) * sizeof (char *));
! 	elts = elements (envp);
! 
! 	// Initialize so we can gracefully recover.
! 	for (int i = 0; i <= envp->length; ++i)
! 	  env[i] = NULL;
! 
! 	for (int i = 0; i < envp->length; ++i)
! 	  env[i] = new_string (elts[i]);
! 	env[envp->length] = NULL;
!       }
! 
!     // We allocate this here because we can't call malloc() after
!     // the fork.
!     if (dir != NULL)
!       path = new_string (dir->getPath ());
! 
!     // Create pipes for I/O.  MSGP is for communicating exec()
!     // status.
!     if (pipe (inp) || pipe (outp) || pipe (errp) || pipe (msgp)
! 	|| fcntl (msgp[1], F_SETFD, FD_CLOEXEC))
!       throw new IOException (JvNewStringLatin1 (strerror (errno)));
! 
!     // We create the streams before forking.  Otherwise if we had an
!     // error while creating the streams we would have run the child
!     // with no way to communicate with it.
!     errorStream =
!       new FileInputStream (new
!                            FileChannelImpl (errp[0], FileChannelImpl::READ));
!     inputStream =
!       new FileInputStream (new
!                            FileChannelImpl (inp[0], FileChannelImpl::READ));
!     outputStream =
!       new FileOutputStream (new FileChannelImpl (outp[1],
!                                              FileChannelImpl::WRITE));
! 
!     // We don't use vfork() because that would cause the local
!     // environment to be set by the child.
! 
!     // Use temporary for fork result to avoid dirtying an extra page.
!     pid_t pid_tmp;
!     if ((pid_tmp = fork ()) == -1)
!       throw new IOException (JvNewStringLatin1 (strerror (errno)));
! 
!     if (pid_tmp == 0)
!       {
! 	// Child process, so remap descriptors, chdir and exec.
! 	if (envp)
! 	  {
! 	    // Preserve PATH and LD_LIBRARY_PATH unless specified
! 	    // explicitly.
! 	    char * path_val = getenv ("PATH");
! 	    char * ld_path_val = getenv ("LD_LIBRARY_PATH");
! 	    environ = env;
! 	    if (path_val && getenv ("PATH") == NULL)
! 	      {
! 		char * path_env =
!                   (char *) _Jv_Malloc (strlen (path_val) + 5 + 1);
! 		strcpy (path_env, "PATH=");
! 		strcat (path_env, path_val);
! 		putenv (path_env);
! 	      }
! 	    if (ld_path_val && getenv ("LD_LIBRARY_PATH") == NULL)
! 	      {
! 		char * ld_path_env =
!                   (char *) _Jv_Malloc (strlen (ld_path_val) + 16 + 1);
! 		strcpy (ld_path_env, "LD_LIBRARY_PATH=");
! 		strcat (ld_path_env, ld_path_val);
! 		putenv (ld_path_env);
! 	      }
! 	  }
! 
! 	// We ignore errors from dup2 because they should never occur.
! 	dup2 (outp[0], 0);
! 	dup2 (inp[1], 1);
! 	dup2 (errp[1], 2);
! 
! 	// Use close and not myclose -- we're in the child, and we
! 	// aren't worried about the possible race condition.
! 	close (inp[0]);
! 	close (inp[1]);
! 	close (errp[0]);
! 	close (errp[1]);
! 	close (outp[0]);
! 	close (outp[1]);
! 	close (msgp[0]);
! 
! 	// Change directory.
! 	if (path != NULL)
! 	  {
! 	    if (chdir (path) != 0)
! 	      {
! 		char c = errno;
! 		write (msgp[1], &c, 1);
! 		_exit (127);
! 	      }
! 	  }
! 
! 	execvp (args[0], args);
! 
! 	// Send the parent notification that the exec failed.
! 	char c = errno;
! 	write (msgp[1], &c, 1);
! 	_exit (127);
!       }
! 
!     // Parent.  Close extra file descriptors and mark ours as
!     // close-on-exec.
!     pid = (jlong) pid_tmp;
! 
!     myclose (outp[0]);
!     myclose (inp[1]);
!     myclose (errp[1]);
!     myclose (msgp[1]);
! 
!     char c;
!     int r = read (msgp[0], &c, 1);
!     if (r == -1)
!       throw new IOException (JvNewStringLatin1 (strerror (errno)));
!     else if (r != 0)
!       throw new IOException (JvNewStringLatin1 (strerror (c)));
!   }
!   catch (java::lang::Throwable * thrown)
!   {
!     // Do some cleanup we only do on failure.  If a stream object
!     // has been created, we must close the stream itself (to avoid
!     // duplicate closes when the stream object is collected).
!     // Otherwise we simply close the underlying file descriptor.
!     // We ignore errors here as they are uninteresting.
  
!     try
!     {
!       if (inputStream != NULL)
! 	inputStream->close ();
!       else
! 	myclose (inp[0]);
      }
!     catch (java::lang::Throwable * ignore)
      {
!     }
  
!     try
!     {
!       if (outputStream != NULL)
! 	outputStream->close ();
!       else
! 	myclose (outp[1]);
!     }
!     catch (java::lang::Throwable * ignore)
!     {
!     }
  
!     try
!     {
!       if (errorStream != NULL)
! 	errorStream->close ();
!       else
! 	myclose (errp[0]);
!     }
!     catch (java::lang::Throwable * ignore)
!     {
!     }
  
!     // These are potentially duplicate, but it doesn't matter due to
!     // the use of myclose.
!     myclose (outp[0]);
!     myclose (inp[1]);
!     myclose (errp[1]);
!     myclose (msgp[1]);
  
!     exception = thrown;
!   }
  
    myclose (msgp[0]);
    cleanup (args, env, path);
  
!   if (exception == NULL)
      {
        fcntl (outp[1], F_SETFD, FD_CLOEXEC);
        fcntl (inp[0], F_SETFD, FD_CLOEXEC);

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]