Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
scala.sys.process.ProcessImpl.scala Maven / Gradle / Ivy
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.sys.process
import processInternal._
import java.util.concurrent.LinkedBlockingQueue
import java.io.{PipedInputStream, PipedOutputStream}
import scala.annotation.tailrec
private[process] trait ProcessImpl {
self: Process.type =>
/** Runs provided code in a new Thread and returns the Thread instance. */
private[process] object Spawn {
def apply(prefix: String, daemon: Boolean = false)(f: => Unit): Thread = {
val thread = new Thread() { override def run() = f }
thread.setName(prefix + "-spawn-" + thread.getName)
thread.setDaemon(daemon)
thread.start()
thread
}
}
private[process] object Future {
def apply[T](f: => T): (Thread, () => T) = {
val result = new LinkedBlockingQueue[Either[Throwable, T]](1)
def run(): Unit = {
val value = try Right(f) catch { case e: Exception => Left(e) }
result.put(value)
}
val t = Spawn("Future")(run())
(t, () => result.take() match {
case Right(value) => value
case Left(exception) => throw exception
})
}
}
private[process] class AndProcess(
a: ProcessBuilder,
b: ProcessBuilder,
io: ProcessIO
) extends SequentialProcess(a, b, io, _ == 0)
private[process] class OrProcess(
a: ProcessBuilder,
b: ProcessBuilder,
io: ProcessIO
) extends SequentialProcess(a, b, io, _ != 0)
private[process] class ProcessSequence(
a: ProcessBuilder,
b: ProcessBuilder,
io: ProcessIO
) extends SequentialProcess(a, b, io, _ => true)
private[process] class SequentialProcess(
a: ProcessBuilder,
b: ProcessBuilder,
io: ProcessIO,
evaluateSecondProcess: Int => Boolean
) extends CompoundProcess {
protected[this] override def runAndExitValue() = {
val first = a.run(io)
runInterruptible(first.exitValue())(first.destroy()) flatMap { codeA =>
if (evaluateSecondProcess(codeA)) {
val second = b.run(io)
runInterruptible(second.exitValue())(second.destroy())
}
else Some(codeA)
}
}
}
private[process] abstract class BasicProcess extends Process {
def start(): Unit
}
private[process] abstract class CompoundProcess extends BasicProcess {
def isAlive() = processThread.isAlive()
def destroy() = destroyer()
def exitValue() = futureValue() getOrElse scala.sys.error("No exit code: process destroyed.")
def start() = { futureThread ;() }
protected lazy val (processThread, (futureThread, futureValue), destroyer) = {
val code = new LinkedBlockingQueue[Option[Int]](1)
val thread = Spawn("CompoundProcess") {
var value: Option[Int] = None
try value = runAndExitValue()
catch {
case _: IndexOutOfBoundsException
| _: IOException
| _: NullPointerException
| _: SecurityException
| _: UnsupportedOperationException
=> value = Some(-1)
}
finally code.put(value)
}
(
thread,
Future(code.take()), // thread.join()
() => thread.interrupt()
)
}
/** Start and block until the exit value is available and then return it in Some. Return None if destroyed (use 'run')*/
protected[this] def runAndExitValue(): Option[Int]
protected[this] def runInterruptible[T](action: => T)(destroyImpl: => Unit): Option[T] = {
try Some(action)
catch onInterrupt { destroyImpl; None }
}
}
private[process] class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean) extends CompoundProcess {
protected def newSource: PipeSource = new PipeSource(a.toString)
protected def newSink: PipeSink = new PipeSink(b.toString)
protected[this] override def runAndExitValue() = runAndExitValue(newSource, newSink)
protected[this] def runAndExitValue(source: PipeSource, sink: PipeSink): Option[Int] = {
source connectOut sink
source.start()
sink.start()
/* Release PipeSource, PipeSink and Process in the correct order.
* If once connect Process with Source or Sink, then the order of releasing them
* must be Source -> Sink -> Process, otherwise IOException will be thrown.
*/
def releaseResources(so: PipeSource, sk: PipeSink, ps: Process*) = {
so.release()
sk.release()
ps.foreach(_.destroy())
}
val firstIO =
if (toError) defaultIO.withError(source.connectIn)
else defaultIO.withOutput(source.connectIn)
val secondIO = defaultIO.withInput(sink.connectOut)
val second =
try b.run(secondIO)
catch onError { err =>
releaseResources(source, sink)
throw err
}
val first =
try a.run(firstIO)
catch onError { err =>
releaseResources(source, sink, second)
throw err
}
runInterruptible {
val exit1 = first.exitValue()
source.done()
source.join()
val exit2 = second.exitValue()
sink.done()
// Since file redirection (e.g. #>) is implemented as a piped process,
// we ignore its exit value so cmd #> file doesn't always return 0.
if (b.hasExitValue) exit2 else exit1
} {
releaseResources(source, sink, first, second)
}
}
}
private[process] abstract class PipeThread(isSink: Boolean, labelFn: () => String) extends Thread {
def run(): Unit
private[process] def runloop(src: InputStream, dst: OutputStream): Unit = {
try BasicIO.transferFully(src, dst)
catch ioFailure(ioHandler)
finally BasicIO close {
if (isSink) dst else src
}
}
private def ioHandler(e: IOException): Unit = e.printStackTrace()
}
private[process] class PipeSource(label: => String) extends PipeThread(false, () => label) {
setName(s"PipeSource($label)-$getName")
protected[this] val pipe = new PipedOutputStream
protected[this] val source = new LinkedBlockingQueue[Option[InputStream]](1)
override final def run(): Unit = {
@tailrec def go(): Unit =
source.take() match {
case Some(in) => runloop(in, pipe) ; go()
case None =>
}
try go()
catch onInterrupt(())
finally BasicIO close pipe
}
def connectIn(in: InputStream): Unit = source.put(Some(in))
def connectOut(sink: PipeSink): Unit = sink connectIn pipe
def release(): Unit = {
interrupt()
done()
join()
}
def done() = source.put(None)
}
private[process] class PipeSink(label: => String) extends PipeThread(true, () => label) {
setName(s"PipeSink($label)-$getName")
protected[this] val pipe = new PipedInputStream
protected[this] val sink = new LinkedBlockingQueue[Option[OutputStream]](1)
override def run(): Unit = {
@tailrec def go(): Unit =
sink.take() match {
case Some(out) => runloop(pipe, out) ; go()
case None =>
}
try go()
catch onInterrupt(())
finally BasicIO close pipe
}
def connectOut(out: OutputStream): Unit = sink.put(Some(out))
def connectIn(pipeOut: PipedOutputStream): Unit = pipe connect pipeOut
def release(): Unit = {
interrupt()
done()
join()
}
def done() = sink.put(None)
}
/** A thin wrapper around a java.lang.Process. `ioThreads` are the Threads created to do I/O.
* The implementation of `exitValue` waits until these threads die before returning.
*/
private[process] class DummyProcess(action: => Int) extends Process {
private[this] val (thread, value) = Future(action)
override def isAlive() = thread.isAlive()
override def exitValue() = value()
override def destroy(): Unit = { }
}
/** 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. It may be null if stdin was inherited.
*
* The implementation of `exitValue` interrupts `inputThread`
* and then waits until all I/O threads die before returning.
*/
private[process] class SimpleProcess(p: JProcess, inputThread: Thread, outputThreads: List[Thread]) extends Process {
override def isAlive() = p.isAlive()
override def exitValue() = {
try p.waitFor() // wait for the process to terminate
finally interrupt()
outputThreads foreach (_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this)
p.exitValue()
}
override def destroy() = {
try {
outputThreads foreach (_.interrupt()) // on destroy, don't bother consuming any more output
p.destroy()
}
finally interrupt()
}
// we interrupt the input thread to notify it that it can terminate
private[this] def interrupt(): Unit = if (inputThread != null) inputThread.interrupt()
}
private[process] final class ThreadProcess(thread: Thread, success: LinkedBlockingQueue[Boolean]) extends Process {
override def isAlive() = thread.isAlive()
override def exitValue() = if (success.take()) 0 else 1 // thread.join()
override def destroy() = thread.interrupt()
}
}