blob: 8a5d16b83959724516096d89d3a79bf8d2d9dd39 (
plain) (
tree)
|
|
package dotty
package tools
package dotc
package vulpix
import java.io.{
File => JFile,
InputStream, ObjectInputStream,
OutputStream, ObjectOutputStream
}
import java.util.concurrent.TimeoutException
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
import scala.concurrent.ExecutionContext.Implicits.global
import scala.collection.mutable
trait RunnerOrchestration {
/** The maximum amount of active runners, which contain a child JVM */
val numberOfSlaves: Int
/** The maximum duration the child process is allowed to consume before
* getting destroyed
*/
val maxDuration: Duration
/** Destroy and respawn process after each test */
val safeMode: Boolean
/** Running a `Test` class's main method from the specified `dir` */
def runMain(dir: JFile): Status = monitor.runMain(dir)
private[this] val monitor = new RunnerMonitor
private class RunnerMonitor {
def runMain(dir: JFile): Status = withRunner(_.runMain(dir))
private class Runner(private var process: Process) {
private[this] val ois = new ObjectInputStream(process.getInputStream)
private[this] val oos = new ObjectOutputStream(process.getOutputStream)
def kill(): Unit = {
if (process ne null) process.destroy()
process = null
}
def runMain(dir: JFile): Status = {
assert(process ne null,
"Runner was killed and then reused without setting a new process")
// Makes the encapsulating RunnerMonitor spawn a new runner
def respawn(): Unit = {
process.destroy()
process = createProcess
}
// pass file to running process
oos.writeObject(dir)
// Create a future reading the object:
val readObject = Future(ois.readObject().asInstanceOf[Status])
// Await result for `maxDuration` and then timout and destroy the
// process:
val status =
try Await.result(readObject, maxDuration)
catch { case _: TimeoutException => { Timeout } }
// Handle failure of the VM:
status match {
case _ if safeMode => respawn()
case status: Failure => respawn()
case Timeout => respawn()
case _ => ()
}
// return run status:
status
}
}
private def createProcess: Process = ???
private[this] val allRunners =
List.fill(numberOfSlaves)(new Runner(createProcess))
private[this] val freeRunners = mutable.Queue(allRunners: _*)
private[this] val busyRunners = mutable.Set.empty[Runner]
private def getRunner(): Runner = synchronized {
while (freeRunners.isEmpty) wait()
val runner = freeRunners.dequeue()
busyRunners += runner
notify()
runner
}
private def freeRunner(runner: Runner): Unit = synchronized {
freeRunners.enqueue(runner)
busyRunners -= runner
notify()
}
private def withRunner[T](op: Runner => T): T = {
val runner = getRunner()
val result = op(runner)
freeRunner(runner)
result
}
private def killAll(): Unit = allRunners.foreach(_.kill())
// On shutdown, we need to kill all runners:
sys.addShutdownHook(killAll())
// If for some reason the test runner (i.e. sbt) doesn't kill the VM, we
// need to clean up ourselves.
SummaryReport.addCleanup(killAll)
}
}
|