Index: src/java/org/apache/nutch/util/CommandRunner.java
===================================================================
--- src/java/org/apache/nutch/util/CommandRunner.java	(revision 355310)
+++ src/java/org/apache/nutch/util/CommandRunner.java	(working copy)
@@ -18,6 +18,7 @@
  * Adopted by John Xing for Nutch Project from
  * http://blog.fivesight.com/prb/space/Call+an+External+Command+from+Java/,
  * which explains the code in detail.
+ * [Original author is moving his site to http://mult.ifario.us/   -peb]
  *
  * Comments by John Xing on 20040621:
  * (1) EDU.oswego.cs.dl.util.concurrent.* is in j2sdk 1.5 now.
@@ -24,8 +25,25 @@
  *     Modifications are needed if we move to j2sdk 1.5.
  * (2) The original looks good, not much to change.
  *
- * This code is in the public domain and comes with no warranty.  
+ * This code is in the public domain and comes with no warranty.
+ */
+
+/*
+ * Comments by Paul Baclace on 20051207:
+ *   I encountered a case where the JVM of a Tasktracker child did not exit
+ * after the main thread returned; a thread dump showed only the threads named
+ * STDOUT and STDERR from CommandRunner as non-daemon threads, and both were
+ * doing a read().  CommandRunner also had an excessively costly busy loop.
+ * These problems were fixed by:
+ * 1. The pipe io threads should be daemons.
+ * 2. The main thread should always interrupt() the pipe io threads when
+ *    finishing up, not just when a timeout occurs.
+ * 3. Sleep before testing whether the process has finished with
+ *    Process.exitValue().
+ * 4. Increased the sleep time to be 1000msec.
+ *
  */
+
 package org.apache.nutch.util;
 
 import java.io.IOException;
@@ -31,6 +49,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.InterruptedIOException;
 
 import EDU.oswego.cs.dl.util.concurrent.BrokenBarrierException;
 import EDU.oswego.cs.dl.util.concurrent.CyclicBarrier;
@@ -80,8 +99,16 @@
   }
 
   public void evaluate() throws IOException {
-    Process proc = Runtime.getRuntime().exec(_command);
+      this.exec();
+  }
 
+  /**
+   *
+   * @return process exit value (return code) or -1 if timed out.
+   * @throws IOException
+   */
+  public int exec() throws IOException {
+    Process proc = Runtime.getRuntime().exec(_command);
     _barrier = new CyclicBarrier(3 + ((_stdin != null) ? 1 : 0));
 
     PullerThread so =
@@ -86,6 +113,7 @@
 
     PullerThread so =
       new PullerThread("STDOUT", proc.getInputStream(), _stdout);
+    so.setDaemon(true);
     so.start();
 
     PullerThread se =
@@ -90,6 +118,7 @@
 
     PullerThread se =
       new PullerThread("STDERR", proc.getErrorStream(), _stderr);
+    se.setDaemon(true);
     se.start();
 
     PusherThread si = null;
@@ -95,6 +124,7 @@
     PusherThread si = null;
     if (_stdin != null) {
       si = new PusherThread("STDIN", _stdin, proc.getOutputStream());
+      si.setDaemon(true);
       si.start();
     }
 
@@ -101,19 +131,15 @@
     boolean _timedout = false;
     long end = System.currentTimeMillis() + _timeout * 1000;
 
+    //
     try {
       if (_timeout == 0) {
-        _barrier.barrier();
+        _barrier.barrier(); // JDK 1.5: // _barrier.await();
       } else {
-        _barrier.attemptBarrier(_timeout * 1000);
+        _barrier.attemptBarrier(_timeout * 1000); // JDK 1.5: //  _barrier.await(_timeout, TimeUnit.SECONDS);
       }
     } catch (TimeoutException ex) {
       _timedout = true;
-      if (si != null) {
-        si.interrupt();
-      }
-      so.interrupt();
-      se.interrupt();
       if (_destroyOnTimeout) {
         proc.destroy();
       }
@@ -123,6 +149,13 @@
       /* IGNORE */
     }
 
+    // tell the io threads we are finished
+    if (si != null) {
+      si.interrupt();
+    }
+    so.interrupt();
+    se.interrupt();
+
     _xit = -1;
 
     if (!_timedout) {
@@ -129,10 +162,14 @@
       if (_waitForExit) {
         do {
           try {
+            Thread.sleep(1000);
             _xit = proc.exitValue();
-            Thread.sleep(250);
           } catch (InterruptedException ie) {
-            /* IGNORE */
+              if (Thread.interrupted()) {
+                  break; // stop waiting on an interrupt for this thread
+              } else {
+                  continue;
+              }
           } catch (IllegalThreadStateException iltse) {
             continue;
           }
@@ -152,6 +189,7 @@
         proc.destroy();
       }
     }
+    return _xit;
   }
 
   public Throwable getThrownError() {
@@ -163,8 +201,6 @@
     private OutputStream _os;
     private InputStream _is;
 
-    private volatile boolean _kaput;
-
     private boolean _closeInput;
 
     protected PumperThread(
@@ -179,7 +215,6 @@
     }
 
     public void run() {
-      _kaput = false;
       try {
         byte[] buf = new byte[BUF];
         int read = 0;
@@ -189,9 +224,10 @@
           _os.write(buf, 0, read);
           _os.flush();
         }
+      } catch (InterruptedIOException iioe) {
+        // ignored
       } catch (Throwable t) {
         _thrownError = t;
-        return;
       } finally {
         try {
           if (_closeInput) {
@@ -203,13 +239,6 @@
           /* IGNORE */
         }
       }
-      try {
-        _barrier.barrier();
-      } catch (InterruptedException ie) {
-        /* IGNORE */
-      } catch (BrokenBarrierException bbe) {
-        /* IGNORE */
-      }
     }
   }
 
@@ -254,7 +283,7 @@
     String filePath = null;
     int timeout = 10;
 
-    String usage = "Usage: CommandRunner [-timeout timeout] commandPath filePath";
+    String usage = "Usage: CommandRunner [-timeout timeoutSecs] commandPath filePath";
 
     if (args.length < 2) {
       System.err.println(usage);
