From 8d123081a20488cfc76bbf59cb8114f6ad04b368 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Wed, 16 May 2012 11:59:42 +0400 Subject: [PATCH] Use java 7 Redirect.INHERIT to inherit subprocess' input stream. --- util/process/InheritInput.scala | 20 ++++++++++++++++++++ util/process/ProcessImpl.scala | 33 +++++++++++++++++---------------- 2 files changed, 37 insertions(+), 16 deletions(-) create mode 100755 util/process/InheritInput.scala diff --git a/util/process/InheritInput.scala b/util/process/InheritInput.scala new file mode 100755 index 000000000..bd45ebe62 --- /dev/null +++ b/util/process/InheritInput.scala @@ -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) +} diff --git a/util/process/ProcessImpl.scala b/util/process/ProcessImpl.scala index d14b64f78..cea74272f 100644 --- a/util/process/ProcessImpl.scala +++ b/util/process/ProcessImpl.scala @@ -379,40 +379,41 @@ private[sbt] class SimpleProcessBuilder(p: JProcessBuilder) extends AbstractProc { 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} // 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 errorThread = if(!p.redirectErrorStream) Spawn(processError(process.getErrorStream)) :: Nil else Nil - new SimpleProcess(process, inThread, outThread :: errorThread) + new SimpleProcess(process, outThread :: errorThread) } override def toString = p.command.toString 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 -* the process. -* The implementation of `exitValue` interrupts `inputThread` and then waits until all I/O threads die before -* returning. */ -private class SimpleProcess(p: JProcess, inputThread: Thread, outputThreads: List[Thread]) extends Process + +/** A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the +* output and error streams of the process. +* The implementation of `exitValue` wait for the process to finish and then waits until the threads reading output and error streams die before +* returning. Note that the thread that reads the input stream cannot be interrupted, see https://github.com/harrah/xsbt/issues/327 and +* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4514257 */ +private class SimpleProcess(p: JProcess, outputThreads: List[Thread]) extends Process { override def exitValue() = { - try { p.waitFor() }// wait for the process to terminate - finally { inputThread.interrupt() } // we interrupt the input thread to notify it that it can terminate + def waitDone(): Unit = + 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) p.exitValue() } - override def destroy() = - { - try { p.destroy() } - finally { inputThread.interrupt() } - } + override def destroy() = p.destroy() } private class FileOutput(file: File, append: Boolean) extends OutputStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath)