db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From krist...@apache.org
Subject svn commit: r1327102 - in /db/derby/code/branches/10.8: ./ java/client/org/apache/derby/client/net/ java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/ java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ java/testing/org/apac...
Date Tue, 17 Apr 2012 14:01:48 GMT
Author: kristwaa
Date: Tue Apr 17 14:01:48 2012
New Revision: 1327102

URL: http://svn.apache.org/viewvc?rev=1327102&view=rev
Log:
DERBY-5608:BaseTestCase.readProcessOutput should read getInputStream() and getErrorStream()
in separate threads
DERBY-5617: Improve process handling in SpawnedProcess

Backported fixes from trunk (revisions 1242681,1244444,1291631,1291647,1294592).
Had to manually fix the output generated by BaseTestCase.readProcessOutput,
since tags have been added in trunk. These tags, for instance "<STDERR>", 
caused some tests to fail on 10.8.
Another change introduced by the backport is that stderr is also appended to
the output.

Modified:
    db/derby/code/branches/10.8/   (props changed)
    db/derby/code/branches/10.8/java/client/org/apache/derby/client/net/NetCursor.java   (props
changed)
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/Driver40UnbootedTest.java
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java

Propchange: db/derby/code/branches/10.8/
------------------------------------------------------------------------------
  Merged /db/derby/code/trunk:r1242681,1244444,1291631,1291647,1294592

Propchange: db/derby/code/branches/10.8/java/client/org/apache/derby/client/net/NetCursor.java
------------------------------------------------------------------------------
  Merged /db/derby/code/trunk/java/client/org/apache/derby/client/net/NetCursor.java:r1242681,1244444,1291631,1291647,1294592

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java?rev=1327102&r1=1327101&r2=1327102&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
(original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
Tue Apr 17 14:01:48 2012
@@ -498,7 +498,7 @@ public class SecureServerTest extends Ba
                 commandSpecifics);
         
         // Ensure it completes without failures.
-        assertEquals(0, spawned.complete(false));
+        assertEquals(0, spawned.complete());
         
         return spawned.getFullServerOutput();
     }

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/Driver40UnbootedTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/Driver40UnbootedTest.java?rev=1327102&r1=1327101&r2=1327102&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/Driver40UnbootedTest.java
(original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/Driver40UnbootedTest.java
Tue Apr 17 14:01:48 2012
@@ -156,7 +156,7 @@ public class Driver40UnbootedTest extend
         SpawnedProcess spawned = new SpawnedProcess( process, "UnbootedTest" );
         
         // Ensure it completes without failures.
-        assertEquals(0, spawned.complete(false));
+        assertEquals(0, spawned.complete());
 
         assertEquals( SUCCESS, spawned.getFullServerOutput() );
     }

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java?rev=1327102&r1=1327101&r2=1327102&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
(original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
Tue Apr 17 14:01:48 2012
@@ -28,12 +28,9 @@ import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
 import java.io.Reader;
 import java.io.PrintStream;
 import java.io.PrintWriter;
@@ -768,45 +765,31 @@ public abstract class BaseTestCase
         return ("IBM Corporation".equals(
                 getSystemProperty("java.vendor")));
     }
-    
-   /**
-    * Reads output from a process and returns it as a string.
-    * This will block until the process terminates.
-    * 
-    * @param pr a running process
-    * @return output of the process
-    * @throws InterruptedException
-    */
-   public static String readProcessOutput(Process pr) throws InterruptedException {
-		InputStream is = pr.getInputStream();
-		if (is == null) {
-			fail("Unexpectedly receiving no text from the process");
-		}
 
-		String output = "";
-		try {
-		    char[] ca = new char[1024];
-		    // Create an InputStreamReader with default encoding; we're hoping
-		    // this to be en. If not, we may not match the expected string.
-		    InputStreamReader inStream;
-		    inStream = new InputStreamReader(is);
-
-		    // keep reading from the stream until all done
-		    int charsRead;
-		    while ((charsRead = inStream.read(ca, 0, ca.length)) != -1)
-		    {
-		        output = output + new String(ca, 0, charsRead);
-		    }
-		} catch (Exception e) {
-		    fail("Exception accessing inputstream from process", e);
-		}
+    /**
+     * Reads output from a process and returns it as a string.
+     * <p>
+     * This will block until the process terminates.
+     * 
+     * @param pr a running process
+     * @return Output of the process, both STDOUT and STDERR.
+     * @throws InterruptedException if interrupted while waiting for the
+     *      subprocess or one of the output collector threads to terminate
+     */
+    public static String readProcessOutput(Process pr)
+            throws InterruptedException {
+        SpawnedProcess wrapper = new SpawnedProcess(pr, "readProcessOutput");
+        wrapper.suppressOutputOnComplete();
+        try {
+            wrapper.complete();
+        } catch (IOException ioe) {
+            fail("process completion method failed", ioe);
+        }
+        String output = wrapper.getFullServerOutput();
+        output += wrapper.getFullServerError();
+        return output;
+    }
 
-		// wait until the process exits
-		pr.waitFor();
-		
-		return output;
-	}
-   
     /**
      * Remove the directory and its contents.
      * @param path Path of the directory

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java?rev=1327102&r1=1327101&r2=1327102&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
(original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
Tue Apr 17 14:01:48 2012
@@ -203,7 +203,7 @@ final public class NetworkServerTestSetu
                     // Dump the output from the spawned process
                     // and destroy it.
                     if (spawnedServer != null) {
-                        spawnedServer.complete(true);
+                        spawnedServer.complete(2000);
                         msg = spawnedServer.getFailMessage(msg);
                         spawnedServer = null;
                     }
@@ -451,7 +451,7 @@ final public class NetworkServerTestSetu
                 // Destroy the process if a failed shutdown
                 // to avoid hangs running tests as the complete()
                 // waits for the process to complete.
-                spawnedServer.complete(failedShutdown != null, getWaitTime());
+                spawnedServer.complete(getWaitTime());
                 spawnedServer = null;
             }
             

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java?rev=1327102&r1=1327101&r2=1327102&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java
(original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java
Tue Apr 17 14:01:48 2012
@@ -22,17 +22,88 @@ package org.apache.derbyTesting.junit;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintStream;
 
+import java.util.Timer;
+import java.util.TimerTask;
+
 /**
  * Utility code that wraps a spawned process (Java Process object).
- * Handles the output streams (stderr and stdout) written
- * by the process by spawning off background threads to read
- * them into byte arrays. The class provides access to the
- * output, typically called once the process is complete.
+ * <p>
+ * There are three main aspects handled by this class:
+ * <ul> <li>Draining the output streams of the process.<br/>
+ *          Happens automatically, the output gathered can be accessed with
+ *          {@linkplain #getFailMessage}, {@linkplain #getFullServerError},
+ *          {@linkplain #getFullServerOutput}, and
+ *          {@linkplain #getNextServerOutput}</li>
+ *      <li>Waiting for process completion, followed by cleanup (see
+ *          {@linkplain #complete()} and {@linkplain #complete(long)})</li>
+ *      <li>Forcibly destroying a process that live too long, for instance
+ *          if inter-process communication hangs. This happens automatically
+ *          if a threshold value is exceeded.</li>
+ * </ul>
+ * <p>
+ * <em>Implementation notes</em>: Active waiting is employed when waiting for
+ * the process to complete. This is considered acceptable since the expected
+ * usage pattern is to spawn the process, execute a set of tests, and then
+ * finally asking the process to shut down. Waiting for the process to
+ * complete is the last step, and a process typically lives only for a short
+ * period of time anyway (often only for seconds, seldom more than a few
+ * minutes).
+ * <br/>
+ * Forcibly destroying processes that live too long makes the test run
+ * continue even when facing inter-process communication hangs. The prime
+ * example is when both the client and the server are waiting for the other
+ * party to send data. Since the timeout is very high this feature is intended
+ * to avoid automated test runs from hanging indefinitely, for instance due to
+ * environmental issues affecting the process.
  */
+//@NotThreadSafe
 public final class SpawnedProcess {
 
+    private static final String TAG = "DEBUG: {SpawnedProcess} ";
+    private static Timer KILL_TIMER;
+
+    /**
+     * Property allowing the kill threshold to be overridden.
+     * <p>
+     * Interprets the numeric value as milliseconds, ignored if non-numeric.
+     * Overriding this value may be required if the test machine is extremely
+     * slow, or you want to kill hung processes earlier for some reason.
+     */
+    private static final String KILL_THRESHOLD_PROPERTY =
+            "derby.tests.process.killThreshold";
+    private static final long KILL_THRESHOLD_DEFAULT = 45*60*1000; // 45 minutes
+    /** The maximum allowed time for a process to live. */
+    private static final long KILL_THRESHOLD;
+    static {
+        long tmpThreshold = KILL_THRESHOLD_DEFAULT;
+        String tmp = BaseTestCase.getSystemProperty(KILL_THRESHOLD_PROPERTY);
+        if (tmp != null) {
+            try {
+                tmpThreshold = Long.parseLong(tmp);
+            } catch (NumberFormatException nfe) {
+                // Ignore, use the default set previously.
+                System.err.println(TAG + "Invalid kill threshold: " + tmp);
+            }
+        }
+        KILL_THRESHOLD = tmpThreshold;
+    }
+
+    private static void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException ie) {
+            // Ignore the interrupt. We want to make sure the process
+            // terminates before returning, and we don't want to preserve
+            // the interrupt flag because it causes Derby to shut down. These
+            // are test requirements and don't apply for production code.
+            // Print a notice to stdout.
+            System.out.println(TAG + "Interrupted while sleeping (ignored)");
+        }
+    }
+
     private final String name;
 
     private final Process javaProcess;
@@ -41,14 +112,55 @@ public final class SpawnedProcess {
 
     private final StreamSaver outSaver;
 
+    private boolean suppressOutput;
+
+    private final TimerTask killTask;
+
+    /**
+     * Creates a new wrapper to handle the given process.
+     *
+     * @param javaProcess a (running) process
+     * @param name name to associate with the process
+     */
     public SpawnedProcess(Process javaProcess, String name) {
         this.javaProcess = javaProcess;
         this.name = name;
 
-        errSaver = streamSaver(javaProcess.getErrorStream(), name
+        errSaver = startStreamSaver(javaProcess.getErrorStream(), name
                 .concat(":System.err"));
-        outSaver = streamSaver(javaProcess.getInputStream(), name
+        outSaver = startStreamSaver(javaProcess.getInputStream(), name
                 .concat(":System.out"));
+        killTask = scheduleKill(javaProcess, name);
+    }
+
+    /**
+     * Schedules a task to kill/terminate the task after a predefined timeout.
+     *
+     * @param name name of the process
+     * @param process the process
+     * @return The task object.
+     */
+    private TimerTask scheduleKill(Process process, String name) {
+        synchronized (KILL_THRESHOLD_PROPERTY) {
+            if (KILL_TIMER == null) {
+                // Can't use 1.5 methods yet due to J2ME. Add name later.
+                KILL_TIMER = new Timer(true);
+            }        
+        }
+        TimerTask killer = new ProcessKillerTask(process, name);
+        KILL_TIMER.schedule(killer, KILL_THRESHOLD);
+        return killer;
+    }
+
+    /**
+     * Causes output obtained from the process to be suppressed when
+     * executing the {@code complete}-methods.
+     *
+     * @see #getFullServerOutput() to obtain suppressed output from stdout
+     * @see #getFullServerError() to obtain suppressed output from stderr
+     */
+    public void suppressOutputOnComplete() {
+        suppressOutput = true;
     }
 
     /**
@@ -66,11 +178,11 @@ public final class SpawnedProcess {
      *
      * <p>
      * This method should only be called after the process has completed.
-     * That is, {@link #complete(boolean)} or {@link #complete(boolean, long)}
+     * That is, {@link #complete()} or {@link #complete(long)}
      * should be called first.
      * </p>
      */
-    public String getFullServerOutput() throws Exception {
+    public String getFullServerOutput() throws InterruptedException {
         // First wait until we've read all the output.
         outSaver.thread.join();
 
@@ -80,17 +192,33 @@ public final class SpawnedProcess {
     }
     
     /**
+     * Get the full server error output (stderr) as a string using the default
+     * encoding which is assumed is how it was originally written.
+     * <p>
+     * This method should only be called after the process has completed.
+     * That is, {@link #complete()} or {@link #complete(long)}
+     * should be called first.
+     */
+    public String getFullServerError() throws InterruptedException {
+        // First wait until we've read all the output on stderr.
+        errSaver.thread.join();
+
+        synchronized (this) {
+            return errSaver.stream.toString();
+        }
+    }
+
+    /**
      * Position offset for getNextServerOutput().
      */
     int stdOutReadOffset;
     /**
      * Get the next set of server output (stdout) as a string using the default
-     * encoding which is assumed is how it was orginally
+     * encoding which is assumed is how it was originally
      * written. Assumes a single caller is executing the calls
      * to this method.
      */
-    public String getNextServerOutput() throws Exception
-    {
+    public String getNextServerOutput() {
         byte[] fullData;
         synchronized (this) {
             fullData = outSaver.stream.toByteArray();
@@ -106,9 +234,8 @@ public final class SpawnedProcess {
      * the stderr and stdout for any output written. Allows
      * easier debugging if the reason the process failed is there!
      */
-    public String getFailMessage(String reason) throws InterruptedException
-    {
-        Thread.sleep(500);
+    public String getFailMessage(String reason) {
+        sleep(500);
         StringBuffer sb = new StringBuffer();
         sb.append(reason);
         sb.append(":Spawned ");
@@ -139,77 +266,132 @@ public final class SpawnedProcess {
     }
 
     /**
-     * Complete the process.
-     * @param destroy true to destroy it, false to wait indefinitely to complete 
+     * Waits for the process to terminate.
+     * <p>
+     * This call will block until one of the following conditions are met:
+     * <ul> <li>the process terminates on its own</li>
+     *      <li>the hung-process watchdog mechanism forcibly terminates the
+     *          process (see {@linkplain #scheduleKill})</li>
+     * @return The process exit code.
+     * @throws IOException if printing diagnostics fails
+     */
+    public int complete()
+            throws IOException {
+        return complete(Long.MAX_VALUE);         
+    }
+
+    /**
+     * Waits for the process to terminate, forcibly terminating it if it
+     * takes longer than the specified timeout.
+     * <p>
+     * This call will block until one of the following conditions are met:
+     * <ul> <li>the process terminates on its own</li>
+     *      <li>the timeout is exceeded, at which point the process is
+     *          forcibly destroyed</li>
+     *      <li>the hung-process watchdog mechanism forcibly terminates the
+     *          process (see {@linkplain #scheduleKill})</li>
+     * @return The process exit code.
+     * @throws IOException if printing diagnostics fails
      */
-    public int complete(boolean destroy) throws InterruptedException, IOException {
-        return complete(destroy, -1L);
+    public int complete(long timeout)
+            throws IOException {
+        long start = System.currentTimeMillis();
+        Integer exitCode = null;
+        while (exitCode == null) {
+            try {
+                exitCode = new Integer(javaProcess.exitValue());
+            } catch (IllegalThreadStateException itse) {
+                // This exception means the process is running.
+                if (System.currentTimeMillis() - start > timeout) {
+                    javaProcess.destroy();
+                }
+                sleep(500);
+            }
+        }
+
+        // Clean up
+        killTask.cancel();
+        joinWith(errSaver.thread);
+        joinWith(outSaver.thread);
+        cleanupProcess();
+        printDiagnostics(exitCode.intValue());
+        return exitCode.intValue();
     }
     
     /**
-     * Complete the process.
-     * @param destroy True to destroy it, false to wait for it to complete 
-     * based on timeout.
-     *  
-     * @param timeout milliseconds to wait until finished or else destroy.
-     * -1 don't timeout
-     *  
-     */
-    public int complete(boolean destroy, long timeout) throws InterruptedException, IOException
{
-        int exitCode;
-        if (timeout >= 0 ) {
-            final long start = System.currentTimeMillis();
-            boolean timedOut = true;
-            long totalwait = -1;
-            while (totalwait < timeout) {
-               try  { 
-                   exitCode = javaProcess.exitValue();
-                   //if no exception thrown, exited normally
-                   destroy = timedOut = false;
-                   break;
-               }catch (IllegalThreadStateException ite) {
-                   // Ignore exception, it means that the process is running.
-                   Thread.sleep(1000);
-                   totalwait = System.currentTimeMillis() - start;
-               }
-            }
-            // If we timed out, make sure we try to destroy the process.
-            if (timedOut) {
-                destroy = true;
-            }
-    	}
-        if (destroy)
-            javaProcess.destroy();
+     * Cleans up the process, explicitly closing the streams associated with it.
+     */
+    private void cleanupProcess() {
+        // Doing this is considered best practice.
+        closeStream(javaProcess.getOutputStream());
+        closeStream(javaProcess.getErrorStream());
+        closeStream(javaProcess.getInputStream());
+        javaProcess.destroy();
+    }
 
-        exitCode = javaProcess.waitFor();
+    /**
+     * Prints diagnostics to stdout/stderr if the process failed.
+     *
+     * @param exitCode the exit code of the spawned process
+     * @throws IOException if writing to an output stream fails
+     * @see #suppressOutput
+     */
+    private synchronized void printDiagnostics(int exitCode)
+            throws IOException {
+        // Always write the error, except when suppressed.
+        ByteArrayOutputStream err = errSaver.stream;
+        if (!suppressOutput && err.size() != 0) {
+            System.err.println("START-SPAWNED:" + name + " ERROR OUTPUT:");
+            err.writeTo(System.err);
+            System.err.println("END-SPAWNED  :" + name + " ERROR OUTPUT:");
+        }
 
-        // The process has completed. Wait until we've read all output.
-        outSaver.thread.join();
-        errSaver.thread.join();
+        // Only write contents of stdout if it appears the server
+        // failed in some way, or output is suppressed.
+        ByteArrayOutputStream out = outSaver.stream;
+        if (!suppressOutput && exitCode != 0 && out.size() != 0) {
+            System.out.println("START-SPAWNED:" + name
+                    + " STANDARD OUTPUT: exit code=" + exitCode);
+            out.writeTo(System.out);
+            System.out.println("END-SPAWNED  :" + name
+                    + " STANDARD OUTPUT:");
+        }
+    }
 
-        synchronized (this) {
+    /** Joins up with the specified thread. */
+    private void joinWith(Thread t) {
+        try {
+            t.join();
+        } catch (InterruptedException ie) {
+            // Ignore the interrupt. We want to make sure the process
+            // terminates before returning, and we don't want to preserve
+            // the interrupt flag because it causes Derby to shut down. These
+            // are test requirements and don't apply for production code.
+            // Print a notice to stdout.
+            System.out.println(TAG + "Interrupted while joining " +
+                    "with thread '" + t.toString() + "'");
+        }
+    }
 
-            // Always write the error
-            ByteArrayOutputStream err = errSaver.stream;
-            if (err.size() != 0) {
-                System.err.println("START-SPAWNED:" + name + " ERROR OUTPUT:");
-                err.writeTo(System.err);
-                System.err.println("END-SPAWNED  :" + name + " ERROR OUTPUT:");
-            }
-
-            // Only write the error if it appeared the server
-            // failed in some way.
-            ByteArrayOutputStream out = outSaver.stream;
-            if ((destroy || exitCode != 0) && out.size() != 0) {
-                System.out.println("START-SPAWNED:" + name
-                        + " STANDARD OUTPUT: exit code=" + exitCode);
-                out.writeTo(System.out);
-                System.out.println("END-SPAWNED  :" + name
-                        + " STANDARD OUTPUT:");
+    /**
+     * Closes the specified stream, ignoring any exceptions.
+     *
+     * @param stream stream to close (may be {@code null})
+     */
+    private void closeStream(Object stream) {
+        if (stream instanceof InputStream) {
+            try {
+                ((InputStream)stream).close();
+            } catch (IOException ioe) {
+                // Ignore exception on close
+            }
+        } else if (stream instanceof OutputStream) {
+            try {
+                ((OutputStream)stream).close();
+            } catch (IOException ioe) {
+                // Ignore exception on close
             }
         }
-        
-        return exitCode;
     }
 
     /**
@@ -226,7 +408,15 @@ public final class SpawnedProcess {
         }
     }
 
-    private StreamSaver streamSaver(final InputStream in,
+    /**
+     * Creates and starts a stream saver that reads the specified input stream
+     * in a separate stream.
+     *
+     * @param in input stream to read from
+     * @param name name of the thread
+     * @return A {@code StreamSaver} object.
+     */
+    private StreamSaver startStreamSaver(final InputStream in,
             final String name) {
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream() {
@@ -260,4 +450,56 @@ public final class SpawnedProcess {
 
         return new StreamSaver(out, streamReader);
     }
+
+    /**
+     * A task that will kill the specified process.
+     *
+     * @see #scheduleKill(java.lang.Process, java.lang.String) 
+     */
+    private static class ProcessKillerTask
+        extends TimerTask {
+
+        private final String name;
+        private Process process;
+
+        public ProcessKillerTask(Process process, String name) {
+            this.process = process;
+            this.name = name;
+        }
+
+        public synchronized boolean cancel() {
+            // Since this task will usually be in the timer queue for a long
+            // time, nullify the process reference on cancel to free resources.
+            process = null;
+            return super.cancel();
+        }
+
+        public synchronized void run() {
+            // We may have just been cancelled 
+            if (process == null) {
+                return;
+            }
+
+            System.err.println("DEBUG: Destroying process '" + name + "'");
+            process.destroy();
+            int retriesAllowed = 10;
+            while (retriesAllowed > 0) {
+                try {
+                    int exitCode = process.exitValue();
+                    System.err.println("DEBUG: Destroyed process '" + name +
+                            "', exit code is " + exitCode);
+                    break;
+                } catch (IllegalThreadStateException itse) {
+                    // Sleep for a second and retry.
+                    sleep(1000);
+                    retriesAllowed--;
+                }
+            }
+            if (retriesAllowed == 0) {
+                System.err.println(
+                        "DEBUG: Failed to destroy process '" + name + "'");
+            } 
+            process = null;
+        }
+    }
 }



Mime
View raw message