diff options
Diffstat (limited to 'src')
5 files changed, 170 insertions, 38 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 57e3f904be..99aa1bcad6 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -6,16 +6,13 @@ package scala.tools.nsc import Predef.{ println => _, _ } -import java.io.{ File, PrintWriter, StringWriter, Writer } -import File.pathSeparator -import java.lang.{ reflect, Class, ClassLoader => JavaClassLoader } -import java.net.{ MalformedURLException, URL } -import reflect.InvocationTargetException -import java.util.concurrent.Future - +import java.io.{ PrintWriter } +import java.io.File.pathSeparator +import java.lang.reflect +import java.net.URL import util.{ Set => _, _ } import interpreter._ -import io.{ PlainFile, VirtualDirectory, spawn, callable, newDaemonThreadExecutor } +import io.VirtualDirectory import reporters.{ ConsoleReporter, Reporter } import symtab.{ Flags, Names } import scala.tools.nsc.{ InterpreterResults => IR } @@ -27,7 +24,7 @@ import Exceptional.unwrap import scala.collection.{ mutable, immutable } import scala.collection.mutable.{ ListBuffer, ArrayBuffer } import scala.PartialFunction.{ cond, condOpt } -import scala.util.control.Exception.{ Catcher, catching, catchingPromiscuously, ultimately, unwrapping } +import scala.util.control.Exception.{ ultimately } import scala.reflect.NameTransformer import Interpreter._ @@ -186,10 +183,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - /** An executor service which creates daemon threads. */ - private lazy val lineExecutor = newDaemonThreadExecutor() - private var _currentExecution: Future[String] = null - def currentExecution = _currentExecution + protected def createLineManager(): Line.Manager = new Line.Manager + lazy val lineManager = createLineManager() /** interpreter settings */ lazy val isettings = new InterpreterSettings(this) @@ -580,7 +575,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { if (trees.size == 1) trees.head match { case _:Assign => // we don't want to include assignments case _:TermTree | _:Ident | _:Select => // ... but do want these as valdefs. - return requestFromLine("val %s =\n%s".format(varName, line), synthetic) + requestFromLine("val %s =\n%s".format(varName, line), synthetic) match { + case Right(req) => return Right(req withOriginalLine line) + case x => return x + } case _ => } @@ -829,6 +827,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** One line of code submitted by the user for interpretation */ private class Request(val line: String, val lineName: String, val trees: List[Tree]) { + private var _originalLine: String = null + def withOriginalLine(s: String): this.type = { _originalLine = s ; this } + def originalLine = if (_originalLine == null) line else _originalLine + /** name to use for the object that will compute "line" */ def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX @@ -1023,29 +1025,32 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** load and run the code using reflection */ def loadAndRun: (String, Boolean) = { - try { - val resultValMethod = loadedResultObject getMethod "scala_repl_result" - _currentExecution = lineExecutor submit callable(resultValMethod.invoke(loadedResultObject).toString) - while (!currentExecution.isDone) - Thread.`yield` + import interpreter.Line._ - if (currentExecution.isCancelled) ("Execution interrupted by signal.\n", false) - else (currentExecution.get(), true) - } - catch { - case t: Throwable if bindLastException => - /** We turn off the binding to accomodate ticket #2817 */ - withoutBindingLastException { - val message = - if (opt.richExes) bindExceptionally(unwrap(t)) - else bindUnexceptionally(unwrap(t)) - - (message, false) - } + def handleException(t: Throwable) = { + /** We turn off the binding to accomodate ticket #2817 */ + withoutBindingLastException { + val message = + if (opt.richExes) bindExceptionally(unwrap(t)) + else bindUnexceptionally(unwrap(t)) + + (message, false) + } } - finally { - _currentExecution = null + + try { + val resultValMethod = loadedResultObject getMethod "scala_repl_result" + val execution = lineManager.set(originalLine)(resultValMethod invoke loadedResultObject) + + execution.await() + execution.state match { + case Done => ("" + execution.get(), true) + case Threw => if (bindLastException) handleException(execution.caught()) else throw execution.caught() + case Cancelled => ("Execution interrupted by signal.\n", false) + case Running => ("Execution still running! Seems impossible.", false) + } } + finally lineManager.clear() } override def toString = "Request(line=%s, %s trees)".format(line, trees.size) diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 27e1be208d..82b32fe219 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -118,18 +118,19 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite } ignoring(classOf[Exception]) { SignalManager("INT") = { - val exec = interpreter.currentExecution - if (exec != null) exec.cancel(true) + if (interpreter.lineManager.running) + interpreter.lineManager.cancel() else if (in.currentLine != "") { + // non-empty buffer, so make them hit ctrl-C a second time SignalManager("INT") = onExit() - io.timer(5)(installSigIntHandler()) // restore original + io.timer(5)(installSigIntHandler()) // and restore original handler if they don't } else onExit() } } } - /** Close the interpreter and set the var to <code>null</code>. */ + /** Close the interpreter and set the var to null. */ def closeInterpreter() { if (interpreter ne null) { interpreter.close @@ -144,6 +145,18 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite settings.classpath append addedClasspath interpreter = new Interpreter(settings, out) { + override protected def createLineManager() = new Line.Manager { + override def onRunaway(line: Line[_]): Unit = { + val template = """ + |// She's gone rogue, captain! Have to take her out! + |// Calling Thread.stop on runaway %s with offending code: + |// scala> %s""".stripMargin + + println(template.format(line.thread, line.code)) + line.thread.stop() + in.redrawLine() + } + } override protected def parentClassLoader = settings.explicitParentLoader.getOrElse( classOf[InterpreterLoop].getClassLoader ) } diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala index 014fd62a0c..8ea4d74369 100644 --- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -17,6 +17,7 @@ trait InteractiveReader { protected def readOneLine(prompt: String): String val interactive: Boolean def init(): Unit = () + def redrawLine(): Unit = () def currentLine = "" // the current buffer contents, if available def readLine(prompt: String): String = { diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index 1da96de0d9..0d71822172 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala @@ -16,6 +16,11 @@ class JLineReader(interpreter: Interpreter) extends InteractiveReader { override lazy val history = Some(History(consoleReader)) override lazy val completion = Option(interpreter) map (x => new Completion(x)) override def init() = consoleReader.getTerminal().initializeTerminal() + override def redrawLine() = { + consoleReader.flushConsole() + consoleReader.drawLine() + consoleReader.flushConsole() + } val consoleReader = { val r = new jline.ConsoleReader() diff --git a/src/compiler/scala/tools/nsc/interpreter/Line.scala b/src/compiler/scala/tools/nsc/interpreter/Line.scala new file mode 100644 index 0000000000..2dc23aa38a --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/Line.scala @@ -0,0 +1,108 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import java.util.concurrent.locks.ReentrantLock +import scala.tools.nsc.util.Exceptional +import Exceptional.unwrap +import Line._ + +/** Encapsulation of a single line in the repl. The concurrency + * infrastructure arose to deal with signals so SIGINT could be + * trapped without losing the repl session, but it will be useful + * in ways beyond that. Each line obtains a thread and the repl + * waits on a condition indicating that either the line has + * completed or failed. + */ +class Line[+T](val code: String, body: => T) { + private var _state: State = Running + private var _result: Any = null + private var _caught: Throwable = null + private val lock = new ReentrantLock() + private val finished = lock.newCondition() + + private def withLock[T](body: => T) = { + lock.lock() + try body + finally lock.unlock() + } + private def setState(state: State) = withLock { + _state = state + finished.signal() + } + // private because it should be called by the manager. + private def cancel() = if (running) setState(Cancelled) + + // This is where the line thread is created and started. + private val _thread = io.daemonize(true) { + try { + _result = body + setState(Done) + } + catch { + case x => + _caught = x + setState(Threw) + } + } + + def state = _state + def thread = _thread + def alive = thread.isAlive + def runaway = !success && alive + def success = _state == Done + def running = _state == Running + + def caught() = { await() ; _caught } + def get() = { await() ; _result } + def await() = withLock { while (running) finished.await() } +} + +object Line { + // seconds to let a runaway thread live before calling stop() + private val HUNTER_KILLER_DELAY = 5 + + // A line opens in state Running, and will eventually + // transition to Threw (an exception was caught), Cancelled + // (the line was explicitly cancelled, presumably by SIGINT) + // or Done (success). + sealed abstract class State + case object Running extends State + case object Threw extends State + case object Cancelled extends State + case object Done extends State + + class Manager { + /** Override to add behavior for runaway lines. This method will + * be called if a line thread is still running five seconds after + * it has been cancelled. + */ + def onRunaway(line: Line[_]): Unit = () + + private var _current: Option[Line[_]] = None + def current = _current + + def clear() = { + _current foreach (_.cancel()) + _current = None + } + def set[T](code: String)(body: => T) = { + val line = new Line(code, body) + _current = Some(line) + line + } + def running = _current.isDefined + def cancel() = { + current foreach { line => + line.thread.interrupt() + line.cancel() + if (line.runaway) + io.timer(HUNTER_KILLER_DELAY) { if (line.alive) onRunaway(line) } + } + } + } +} |