Use java 7 Redirect.INHERIT to inherit subprocess' input stream.

This commit is contained in:
Eugene Vigdorchik
2012-05-17 07:24:24 -04:00
committed by Mark Harrah
parent 53ee1c5adf
commit 8d123081a2
2 changed files with 37 additions and 16 deletions
+20
View File
@@ -0,0 +1,20 @@
/* sbt -- Simple Build Tool
* Copyright 2012 Eugene Vigdorchik
*/
package sbt
import java.lang.{ProcessBuilder => JProcessBuilder}
/** On java 7, inherit System.in for a ProcessBuilder. */
private[sbt] object InheritInput {
def apply(p: JProcessBuilder): (Boolean, JProcessBuilder) = (redirectInput, inherit) match {
case (Some(m), Some(f)) => (true, m.invoke(p, f).asInstanceOf[JProcessBuilder])
case _ => (false, p)
}
private[this] val pbClass = Class.forName("java.lang.ProcessBuilder")
private[this] val redirectClass = pbClass.getClasses find (_.getSimpleName == "Redirect")
private[this] val redirectInput = redirectClass map (pbClass.getMethod("redirectInput", _))
private[this] val inherit = redirectClass map (_ getField "INHERIT" get null)
}
+17 -16
View File
@@ -379,40 +379,41 @@ private[sbt] class SimpleProcessBuilder(p: JProcessBuilder) extends AbstractProc
{ {
override def run(io: ProcessIO): Process = override def run(io: ProcessIO): Process =
{ {
val process = p.start() // start the external process val (inherited, pp) = InheritInput(p)
val process = pp.start() // start the external process
import io.{writeInput, processOutput, processError} import io.{writeInput, processOutput, processError}
// spawn threads that process the input, output, and error streams using the functions defined in `io` // spawn threads that process the input, output, and error streams using the functions defined in `io`
val inThread = Spawn(writeInput(process.getOutputStream), true) if(!inherited)
Spawn(writeInput(process.getOutputStream), true)
val outThread = Spawn(processOutput(process.getInputStream)) val outThread = Spawn(processOutput(process.getInputStream))
val errorThread = val errorThread =
if(!p.redirectErrorStream) if(!p.redirectErrorStream)
Spawn(processError(process.getErrorStream)) :: Nil Spawn(processError(process.getErrorStream)) :: Nil
else else
Nil Nil
new SimpleProcess(process, inThread, outThread :: errorThread) new SimpleProcess(process, outThread :: errorThread)
} }
override def toString = p.command.toString override def toString = p.command.toString
override def canPipeTo = true override def canPipeTo = true
} }
/** A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the
* output and error streams of the process. `inputThread` is the Thread created to write to the input stream of /** A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the
* the process. * output and error streams of the process.
* The implementation of `exitValue` interrupts `inputThread` and then waits until all I/O threads die before * The implementation of `exitValue` wait for the process to finish and then waits until the threads reading output and error streams die before
* returning. */ * returning. Note that the thread that reads the input stream cannot be interrupted, see https://github.com/harrah/xsbt/issues/327 and
private class SimpleProcess(p: JProcess, inputThread: Thread, outputThreads: List[Thread]) extends Process * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4514257 */
private class SimpleProcess(p: JProcess, outputThreads: List[Thread]) extends Process
{ {
override def exitValue() = override def exitValue() =
{ {
try { p.waitFor() }// wait for the process to terminate def waitDone(): Unit =
finally { inputThread.interrupt() } // we interrupt the input thread to notify it that it can terminate try { p.waitFor() } catch { case _: InterruptedException => waitDone() }
waitDone()
outputThreads.foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this) outputThreads.foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this)
p.exitValue() p.exitValue()
} }
override def destroy() = override def destroy() = p.destroy()
{
try { p.destroy() }
finally { inputThread.interrupt() }
}
} }
private class FileOutput(file: File, append: Boolean) extends OutputStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath) private class FileOutput(file: File, append: Boolean) extends OutputStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath)