4244896: (process) Provide System.getPid(), System.killProcess(String pid)
authorrobm
Tue, 26 Jun 2012 13:27:26 +0100
changeset 13149 27d52f97a5cc
parent 13033 365efcc2d50c
child 13150 17f7fa4a0313
child 13151 efb05678b628
4244896: (process) Provide System.getPid(), System.killProcess(String pid) Reviewed-by: alanb
jdk/src/share/classes/java/lang/Process.java
jdk/src/solaris/classes/java/lang/UNIXProcess.java.bsd
jdk/src/solaris/classes/java/lang/UNIXProcess.java.linux
jdk/src/solaris/classes/java/lang/UNIXProcess.java.solaris
jdk/src/solaris/native/java/lang/UNIXProcess_md.c
jdk/src/windows/classes/java/lang/ProcessImpl.java
jdk/src/windows/native/java/lang/ProcessImpl_md.c
jdk/test/java/lang/ProcessBuilder/Basic.java
jdk/test/java/lang/ProcessBuilder/DestroyTest.java
--- a/jdk/src/share/classes/java/lang/Process.java	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/share/classes/java/lang/Process.java	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
 package java.lang;
 
 import java.io.*;
+import java.util.concurrent.TimeUnit;
 
 /**
  * The {@link ProcessBuilder#start()} and
@@ -91,7 +92,7 @@
      * @return the output stream connected to the normal input of the
      *         subprocess
      */
-    abstract public OutputStream getOutputStream();
+    public abstract OutputStream getOutputStream();
 
     /**
      * Returns the input stream connected to the normal output of the
@@ -117,7 +118,7 @@
      * @return the input stream connected to the normal output of the
      *         subprocess
      */
-    abstract public InputStream getInputStream();
+    public abstract InputStream getInputStream();
 
     /**
      * Returns the input stream connected to the error output of the
@@ -138,7 +139,7 @@
      * @return the input stream connected to the error output of
      *         the subprocess
      */
-    abstract public InputStream getErrorStream();
+    public abstract InputStream getErrorStream();
 
     /**
      * Causes the current thread to wait, if necessary, until the
@@ -156,7 +157,51 @@
      *         thread while it is waiting, then the wait is ended and
      *         an {@link InterruptedException} is thrown.
      */
-    abstract public int waitFor() throws InterruptedException;
+    public abstract int waitFor() throws InterruptedException;
+
+    /**
+     * Causes the current thread to wait, if necessary, until the
+     * subprocess represented by this {@code Process} object has
+     * terminated, or the specified waiting time elapses.
+     *
+     * <p>If the subprocess has already terminated then this method returns
+     * immediately with the value {@code true}.  If the process has not
+     * terminated and the timeout value is less than, or equal to, zero, then
+     * this method returns immediately with the value {@code false}.
+     *
+     * <p>The default implementation of this methods polls the {@code exitValue}
+     * to check if the process has terminated. Concrete implementations of this
+     * class are strongly encouraged to override this method with a more
+     * efficient implementation.
+     *
+     * @param timeout the maximum time to wait
+     * @param unit the time unit of the {@code timeout} argument
+     * @return {@code true} if the subprocess has exited and {@code false} if
+     *         the waiting time elapsed before the subprocess has exited.
+     * @throws InterruptedException if the current thread is interrupted
+     *         while waiting.
+     * @throws NullPointerException if unit is null
+     * @since 1.8
+     */
+    public boolean waitFor(long timeout, TimeUnit unit)
+        throws InterruptedException
+    {
+        long startTime = System.nanoTime();
+        long rem = unit.toNanos(timeout);
+
+        do {
+            try {
+                exitValue();
+                return true;
+            } catch(IllegalThreadStateException ex) {
+                if (rem > 0)
+                    Thread.sleep(
+                        Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
+            }
+            rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
+        } while (rem > 0);
+        return false;
+    }
 
     /**
      * Returns the exit value for the subprocess.
@@ -167,11 +212,54 @@
      * @throws IllegalThreadStateException if the subprocess represented
      *         by this {@code Process} object has not yet terminated
      */
-    abstract public int exitValue();
+    public abstract int exitValue();
+
+    /**
+     * Kills the subprocess. Whether the subprocess represented by this
+     * {@code Process} object is forcibly terminated or not is
+     * implementation dependent.
+     */
+    public abstract void destroy();
 
     /**
      * Kills the subprocess. The subprocess represented by this
      * {@code Process} object is forcibly terminated.
+     *
+     * <p>The default implementation of this method invokes {@link #destroy}
+     * and so may not forcibly terminate the process. Concrete implementations
+     * of this class are strongly encouraged to override this method with a
+     * compliant implementation.  Invoking this method on {@code Process}
+     * objects returned by {@link ProcessBuilder#start} and
+     * {@link Runtime#exec} will forcibly terminate the process.
+     *
+     * <p>Note: The subprocess may not terminate immediately.
+     * i.e. {@code isAlive()} may return true for a brief period
+     * after {@code destroyForcibly()} is called. This method
+     * may be chained to {@code waitFor()} if needed.
+     *
+     * @return the {@code Process} object representing the
+     *         subprocess to be forcibly destroyed.
+     * @since 1.8
      */
-    abstract public void destroy();
+    public Process destroyForcibly() {
+        destroy();
+        return this;
+    }
+
+    /**
+     * Tests whether the subprocess represented by this {@code Process} is
+     * alive.
+     *
+     * @return {@code true} if the subprocess represented by this
+     *         {@code Process} object has not yet terminated.
+     * @since 1.8
+     */
+    public boolean isAlive() {
+        try {
+            exitValue();
+            return false;
+        } catch(IllegalThreadStateException e) {
+            return true;
+        }
+    }
 }
--- a/jdk/src/solaris/classes/java/lang/UNIXProcess.java.bsd	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/solaris/classes/java/lang/UNIXProcess.java.bsd	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -38,6 +38,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 import java.security.AccessController;
 import static java.security.AccessController.doPrivileged;
 import java.security.PrivilegedAction;
@@ -211,6 +212,24 @@
         }
         return exitcode;
     }
+    
+    @Override
+    public synchronized boolean waitFor(long timeout, TimeUnit unit) 
+        throws InterruptedException 
+    {
+        if (hasExited) return true;
+        if (timeout <= 0) return false;	
+
+        long timeoutAsNanos = unit.toNanos(timeout);
+        long startTime = System.nanoTime();
+        long rem = timeoutAsNanos;
+
+        while (!hasExited && (rem > 0)) {
+            wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1));
+            rem = timeoutAsNanos - (System.nanoTime() - startTime);
+        }
+        return hasExited;
+    }
 
     public synchronized int exitValue() {
         if (!hasExited) {
@@ -219,8 +238,8 @@
         return exitcode;
     }
 
-    private static native void destroyProcess(int pid);
-    public void destroy() {
+    private static native void destroyProcess(int pid, boolean force);
+    private void destroy(boolean force) {
         // There is a risk that pid will be recycled, causing us to
         // kill the wrong process!  So we only terminate processes
         // that appear to still be running.  Even with this check,
@@ -229,13 +248,28 @@
         // soon, so this is quite safe.
         synchronized (this) {
             if (!hasExited)
-                destroyProcess(pid);
+                destroyProcess(pid, force);
         }
         try { stdin.close();  } catch (IOException ignored) {}
         try { stdout.close(); } catch (IOException ignored) {}
         try { stderr.close(); } catch (IOException ignored) {}
     }
 
+    public void destroy() {
+        destroy(false);
+    }
+
+    @Override
+    public Process destroyForcibly() {
+        destroy(true);
+        return this;
+    }
+
+    @Override
+    public synchronized boolean isAlive() {
+        return !hasExited;
+    }
+
     /* This routine initializes JNI field offsets for the class */
     private static native void initIDs();
 
--- a/jdk/src/solaris/classes/java/lang/UNIXProcess.java.linux	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/solaris/classes/java/lang/UNIXProcess.java.linux	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -38,6 +38,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 import java.security.AccessController;
 import static java.security.AccessController.doPrivileged;
 import java.security.PrivilegedAction;
@@ -212,6 +213,24 @@
         return exitcode;
     }
 
+    @Override
+    public synchronized boolean waitFor(long timeout, TimeUnit unit)
+        throws InterruptedException
+    {
+        if (hasExited) return true;
+        if (timeout <= 0) return false;
+
+        long timeoutAsNanos = unit.toNanos(timeout);
+        long startTime = System.nanoTime();
+        long rem = timeoutAsNanos;
+
+        while (!hasExited && (rem > 0)) {
+            wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1));
+            rem = timeoutAsNanos - (System.nanoTime() - startTime);
+        }
+        return hasExited;
+    }
+
     public synchronized int exitValue() {
         if (!hasExited) {
             throw new IllegalThreadStateException("process hasn't exited");
@@ -219,8 +238,8 @@
         return exitcode;
     }
 
-    private static native void destroyProcess(int pid);
-    public void destroy() {
+    private static native void destroyProcess(int pid, boolean force);
+    private void destroy(boolean force) {
         // There is a risk that pid will be recycled, causing us to
         // kill the wrong process!  So we only terminate processes
         // that appear to still be running.  Even with this check,
@@ -229,13 +248,28 @@
         // soon, so this is quite safe.
         synchronized (this) {
             if (!hasExited)
-                destroyProcess(pid);
+                destroyProcess(pid, force);
         }
         try { stdin.close();  } catch (IOException ignored) {}
         try { stdout.close(); } catch (IOException ignored) {}
         try { stderr.close(); } catch (IOException ignored) {}
     }
 
+    public void destroy() {
+        destroy(false);
+    }
+
+    @Override
+    public Process destroyForcibly() {
+        destroy(true);
+        return this;
+    }
+
+    @Override
+    public synchronized boolean isAlive() {
+        return !hasExited;
+    }
+
     /* This routine initializes JNI field offsets for the class */
     private static native void initIDs();
 
--- a/jdk/src/solaris/classes/java/lang/UNIXProcess.java.solaris	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/solaris/classes/java/lang/UNIXProcess.java.solaris	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
 package java.lang;
 
 import java.io.*;
+import java.util.concurrent.TimeUnit;
 
 /* java.lang.Process subclass in the UNIX environment.
  *
@@ -158,6 +159,24 @@
         return exitcode;
     }
 
+    @Override
+    public synchronized boolean waitFor(long timeout, TimeUnit unit)
+        throws InterruptedException
+    {
+        if (hasExited) return true;
+        if (timeout <= 0) return false;
+
+        long timeoutAsNanos = unit.toNanos(timeout);
+        long startTime = System.nanoTime();
+        long rem = timeoutAsNanos;
+
+        while (!hasExited && (rem > 0)) {
+            wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1));
+            rem = timeoutAsNanos - (System.nanoTime() - startTime);
+        }
+        return hasExited;
+    }
+
     public synchronized int exitValue() {
         if (!hasExited) {
             throw new IllegalThreadStateException("process hasn't exited");
@@ -165,8 +184,8 @@
         return exitcode;
     }
 
-    private static native void destroyProcess(int pid);
-    public synchronized void destroy() {
+    private static native void destroyProcess(int pid, boolean force);
+    private synchronized void destroy(boolean force) {
         // There is a risk that pid will be recycled, causing us to
         // kill the wrong process!  So we only terminate processes
         // that appear to still be running.  Even with this check,
@@ -174,7 +193,7 @@
         // is very small, and OSes try hard to not recycle pids too
         // soon, so this is quite safe.
         if (!hasExited)
-            destroyProcess(pid);
+            destroyProcess(pid, force);
         try {
             stdin_stream.close();
             if (stdout_inner_stream != null)
@@ -187,6 +206,21 @@
         }
     }
 
+    public void destroy() {
+        destroy(false);
+    }
+
+    @Override
+    public Process destroyForcibly() {
+        destroy(true);
+        return this;
+    }
+
+    @Override
+    public synchronized boolean isAlive() {
+        return !hasExited;
+    }
+
     // A FileInputStream that supports the deferment of the actual close
     // operation until the last pending I/O operation on the stream has
     // finished.  This is required on Solaris because we must close the stdin
--- a/jdk/src/solaris/native/java/lang/UNIXProcess_md.c	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/solaris/native/java/lang/UNIXProcess_md.c	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -955,7 +955,11 @@
 }
 
 JNIEXPORT void JNICALL
-Java_java_lang_UNIXProcess_destroyProcess(JNIEnv *env, jobject junk, jint pid)
+Java_java_lang_UNIXProcess_destroyProcess(JNIEnv *env,
+                                          jobject junk,
+                                          jint pid,
+                                          jboolean force)
 {
-    kill(pid, SIGTERM);
+    int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
+    kill(pid, sig);
 }
--- a/jdk/src/windows/classes/java/lang/ProcessImpl.java	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/windows/classes/java/lang/ProcessImpl.java	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,6 +37,7 @@
 import java.lang.ProcessBuilder.Redirect;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.concurrent.TimeUnit;
 
 /* This class is for the exclusive use of ProcessBuilder.start() to
  * create new processes.
@@ -254,11 +255,44 @@
             throw new InterruptedException();
         return exitValue();
     }
+
     private static native void waitForInterruptibly(long handle);
 
+    @Override
+    public boolean waitFor(long timeout, TimeUnit unit)
+        throws InterruptedException
+    {
+        if (getExitCodeProcess(handle) != STILL_ACTIVE) return true;
+        if (timeout <= 0) return false;
+
+        long msTimeout = unit.toMillis(timeout);
+
+        waitForTimeoutInterruptibly(handle, msTimeout);
+        if (Thread.interrupted())
+            throw new InterruptedException();
+        return (getExitCodeProcess(handle) != STILL_ACTIVE);
+    }
+
+    private static native void waitForTimeoutInterruptibly(
+        long handle, long timeout);
+
     public void destroy() { terminateProcess(handle); }
+
+    @Override
+    public Process destroyForcibly() {
+        destroy();
+        return this;
+    }
+
     private static native void terminateProcess(long handle);
 
+    @Override
+    public boolean isAlive() {
+        return isProcessAlive(handle);
+    }
+
+    private static native boolean isProcessAlive(long handle);
+
     /**
      * Create a process using the win32 function CreateProcess.
      *
--- a/jdk/src/windows/native/java/lang/ProcessImpl_md.c	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/src/windows/native/java/lang/ProcessImpl_md.c	Tue Jun 26 13:27:26 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -299,18 +299,45 @@
 
     if (WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events,
                                FALSE,    /* Wait for ANY event */
-                               INFINITE) /* Wait forever */
+                               INFINITE)  /* Wait forever */
         == WAIT_FAILED)
         win32Error(env, "WaitForMultipleObjects");
 }
 
 JNIEXPORT void JNICALL
+Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly(JNIEnv *env,
+                                                       jclass ignored,
+                                                       jlong handle,
+                                                       jlong timeout)
+{
+    HANDLE events[2];
+    DWORD dwTimeout = (DWORD)timeout;
+    DWORD result;
+    events[0] = (HANDLE) handle;
+    events[1] = JVM_GetThreadInterruptEvent();
+    result = WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events,
+                                    FALSE,    /* Wait for ANY event */
+                                    dwTimeout);  /* Wait for dwTimeout */
+
+    if (result == WAIT_FAILED)
+        win32Error(env, "WaitForMultipleObjects");
+}
+
+JNIEXPORT void JNICALL
 Java_java_lang_ProcessImpl_terminateProcess(JNIEnv *env, jclass ignored, jlong handle)
 {
     TerminateProcess((HANDLE) handle, 1);
 }
 
 JNIEXPORT jboolean JNICALL
+Java_java_lang_ProcessImpl_isProcessAlive(JNIEnv *env, jclass ignored, jlong handle)
+{
+    DWORD dwExitStatus;
+    GetExitCodeProcess(handle, &dwExitStatus);
+    return dwExitStatus == STILL_ACTIVE;
+}
+
+JNIEXPORT jboolean JNICALL
 Java_java_lang_ProcessImpl_closeHandle(JNIEnv *env, jclass ignored, jlong handle)
 {
     return CloseHandle((HANDLE) handle);
--- a/jdk/test/java/lang/ProcessBuilder/Basic.java	Mon Jun 25 14:19:38 2012 +0100
+++ b/jdk/test/java/lang/ProcessBuilder/Basic.java	Tue Jun 26 13:27:26 2012 +0100
@@ -26,7 +26,7 @@
  * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
  *      5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
  *      6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958
- *      4947220 7018606 7034570
+ *      4947220 7018606 7034570 4244896
  * @summary Basic tests for Process and Environment Variable code
  * @run main/othervm/timeout=300 Basic
  * @author Martin Buchholz
@@ -38,6 +38,7 @@
 import java.io.*;
 import java.util.*;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.security.*;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
@@ -636,6 +637,44 @@
         static boolean is() { return is; }
     }
 
+    static class DelegatingProcess extends Process {
+        final Process p;
+
+        DelegatingProcess(Process p) {
+            this.p = p;
+        }
+
+        @Override
+        public void destroy() {
+            p.destroy();
+        }
+
+        @Override
+        public int exitValue() {
+            return p.exitValue();
+        }
+
+        @Override
+        public int waitFor() throws InterruptedException {
+            return p.waitFor();
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return p.getOutputStream();
+        }
+
+        @Override
+        public InputStream getInputStream() {
+            return p.getInputStream();
+        }
+
+        @Override
+        public InputStream getErrorStream() {
+            return p.getErrorStream();
+        }
+    }
+
     private static boolean matches(String str, String regex) {
         return Pattern.compile(regex).matcher(str).find();
     }
@@ -2090,6 +2129,112 @@
         policy.setPermissions(new RuntimePermission("setSecurityManager"));
         System.setSecurityManager(null);
 
+        //----------------------------------------------------------------
+        // Check that Process.isAlive() &
+        // Process.waitFor(0, TimeUnit.MILLISECONDS) work as expected.
+        //----------------------------------------------------------------
+        try {
+            List<String> childArgs = new ArrayList<String>(javaChildArgs);
+            childArgs.add("sleep");
+            final Process p = new ProcessBuilder(childArgs).start();
+            long start = System.nanoTime();
+            if (!p.isAlive() || p.waitFor(0, TimeUnit.MILLISECONDS)) {
+                fail("Test failed: Process exited prematurely");
+            }
+            long end = System.nanoTime();
+            // give waitFor(timeout) a wide berth (100ms)
+            if ((end - start) > 100000000)
+                fail("Test failed: waitFor took too long");
+
+            p.destroy();
+            p.waitFor();
+
+            if (p.isAlive() ||
+                !p.waitFor(0, TimeUnit.MILLISECONDS))
+            {
+                fail("Test failed: Process still alive - please terminate " +
+                    p.toString() + " manually");
+            }
+        } catch (Throwable t) { unexpected(t); }
+
+        //----------------------------------------------------------------
+        // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS)
+        // works as expected.
+        //----------------------------------------------------------------
+        try {
+            List<String> childArgs = new ArrayList<String>(javaChildArgs);
+            childArgs.add("sleep");
+            final Process p = new ProcessBuilder(childArgs).start();
+            long start = System.nanoTime();
+
+            p.waitFor(1000, TimeUnit.MILLISECONDS);
+
+            long end = System.nanoTime();
+            if ((end - start) < 500000000)
+                fail("Test failed: waitFor didn't take long enough");
+
+            p.destroy();
+
+            start = System.nanoTime();
+            p.waitFor(1000, TimeUnit.MILLISECONDS);
+            end = System.nanoTime();
+            if ((end - start) > 100000000)
+                fail("Test failed: waitFor took too long on a dead process.");
+        } catch (Throwable t) { unexpected(t); }
+
+        //----------------------------------------------------------------
+        // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS)
+        // interrupt works as expected.
+        //----------------------------------------------------------------
+        try {
+            List<String> childArgs = new ArrayList<String>(javaChildArgs);
+            childArgs.add("sleep");
+            final Process p = new ProcessBuilder(childArgs).start();
+            final long start = System.nanoTime();
+
+            final Thread thread = new Thread() {
+                public void run() {
+                    try {
+                        try {
+                            p.waitFor(10000, TimeUnit.MILLISECONDS);
+                        } catch (InterruptedException e) {
+                            return;
+                        }
+                        fail("waitFor() wasn't interrupted");
+                    } catch (Throwable t) { unexpected(t); }}};
+
+            thread.start();
+            Thread.sleep(1000);
+            thread.interrupt();
+            p.destroy();
+        } catch (Throwable t) { unexpected(t); }
+
+        //----------------------------------------------------------------
+        // Check the default implementation for
+        // Process.waitFor(long, TimeUnit)
+        //----------------------------------------------------------------
+        try {
+            List<String> childArgs = new ArrayList<String>(javaChildArgs);
+            childArgs.add("sleep");
+            final Process proc = new ProcessBuilder(childArgs).start();
+            DelegatingProcess p = new DelegatingProcess(proc);
+            long start = System.nanoTime();
+
+            p.waitFor(1000, TimeUnit.MILLISECONDS);
+
+            long end = System.nanoTime();
+            if ((end - start) < 500000000)
+                fail("Test failed: waitFor didn't take long enough");
+
+            p.destroy();
+
+            start = System.nanoTime();
+            p.waitFor(1000, TimeUnit.MILLISECONDS);
+            end = System.nanoTime();
+            // allow for the less accurate default implementation
+            if ((end - start) > 200000000)
+                fail("Test failed: waitFor took too long on a dead process.");
+        } catch (Throwable t) { unexpected(t); }
     }
 
     static void closeStreams(Process p) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessBuilder/DestroyTest.java	Tue Jun 26 13:27:26 2012 +0100
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 4244896
+ * @summary Test for the various platform specific implementations of
+ *          destroyForcibly.
+ */
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+abstract class ProcessTest implements Runnable {
+    ProcessBuilder bldr;
+    Process p;
+
+    public Process killProc(boolean force) throws Exception {
+        if (force) {
+            p.destroyForcibly();
+        } else {
+            p.destroy();
+        }
+        return p;
+    }
+
+    public boolean isAlive() {
+        return p.isAlive();
+    }
+
+    public void run() {
+        try {
+            String line;
+            BufferedReader is =
+                new BufferedReader(new InputStreamReader(p.getInputStream()));
+            while ((line = is.readLine()) != null)
+                System.err.println("ProcessTrap: " + line);
+        } catch(IOException e) {
+            if (!e.getMessage().matches("[Ss]tream [Cc]losed")) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public abstract void runTest() throws Exception;
+}
+
+class UnixTest extends ProcessTest {
+    public UnixTest(File script) throws IOException {
+        script.deleteOnExit();
+        createScript(script);
+        bldr = new ProcessBuilder(script.getCanonicalPath());
+        bldr.redirectErrorStream(true);
+        bldr.directory(new File("."));
+        p = bldr.start();
+    }
+
+    void createScript(File processTrapScript) throws IOException {
+        processTrapScript.deleteOnExit();
+        FileWriter fstream = new FileWriter(processTrapScript);
+        try (BufferedWriter out = new BufferedWriter(fstream)) {
+            out.write("#!/bin/bash\n" +
+                "echo \\\"ProcessTrap.sh started: trapping SIGTERM/SIGINT\\\"\n" +
+                "trap bashtrap SIGTERM SIGINT\n" +
+                "bashtrap()\n" +
+                "{\n" +
+                "    echo \\\"SIGTERM/SIGINT detected!\\\"\n" +
+                "}\n" +
+                "\n" +
+                "while :\n" +
+                "do\n" +
+                "    sleep 1;\n" +
+                "done\n");
+        }
+        processTrapScript.setExecutable(true, true);
+    }
+
+    @Override
+    public void runTest() throws Exception {
+        killProc(false);
+        Thread.sleep(1000);
+        if (!p.isAlive())
+            throw new RuntimeException("Process terminated prematurely.");
+        killProc(true).waitFor();
+        if (p.isAlive())
+            throw new RuntimeException("Problem terminating the process.");
+    }
+}
+
+class MacTest extends UnixTest {
+    public MacTest(File script) throws IOException {
+        super(script);
+    }
+
+    @Override
+    public void runTest() throws Exception {
+        // On Mac, it appears that when we close the processes streams
+        // after a destroy() call, the process terminates with a
+        // SIGPIPE even if it was trapping the SIGTERM, so as with
+        // windows, we skip the trap test and go straight to destroyForcibly().
+        killProc(true).waitFor();
+        if (p.isAlive())
+            throw new RuntimeException("Problem terminating the process.");
+    }
+}
+
+class WindowsTest extends ProcessTest {
+    public WindowsTest() throws IOException {
+        bldr = new ProcessBuilder("ftp");
+        bldr.redirectErrorStream(true);
+        bldr.directory(new File("."));
+        p = bldr.start();
+    }
+
+    @Override
+    public void runTest() throws Exception {
+        killProc(true).waitFor();
+    }
+}
+
+public class DestroyTest {
+
+    public static ProcessTest getTest() throws Exception {
+        String osName = System.getProperty("os.name");
+        if (osName.startsWith("Windows")) {
+            return new WindowsTest();
+        } else if (osName.startsWith("Linux") == true) {
+            return new UnixTest(
+                File.createTempFile("ProcessTrap-", ".sh",null));
+        } else if (osName.startsWith("Mac OS")) {
+            return new MacTest(
+                File.createTempFile("ProcessTrap-", ".sh",null));
+        } else if (osName.equals("SunOS")) {
+            return new UnixTest(
+                File.createTempFile("ProcessTrap-", ".sh",null));
+        }
+        return null;
+    }
+
+    public static void main(String args[]) throws Exception {
+        ProcessTest test = getTest();
+        if (test == null) {
+            throw new RuntimeException("Unrecognised OS");
+        } else {
+            new Thread(test).start();
+            Thread.sleep(1000);
+            test.runTest();
+        }
+    }
+}
+