summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-12-06 20:46:20 +0000
committerPaul Phillips <paulp@improving.org>2010-12-06 20:46:20 +0000
commit1113f7ddca4f1814396e0de9f586c300b135f157 (patch)
tree077b70af447784f580fdcfcdceb505117661500b /src
parent8922c4ed092a2fb4c3f6002f7c49d304f299cd02 (diff)
downloadscala-1113f7ddca4f1814396e0de9f586c300b135f157.tar.gz
scala-1113f7ddca4f1814396e0de9f586c300b135f157.tar.bz2
scala-1113f7ddca4f1814396e0de9f586c300b135f157.zip
The ability to interrupt repl execution without...
The ability to interrupt repl execution without terminating the JVM has snowballed a bit, but it's so handy that it's worth it. This commit further encapsulates individual lines of execution such that they can be managed after the signal comes in, and if necessary, forcibly terminated. I'm straying into territory where I could use a really Thread-knowledgeable reviewer: any volunteers? In the meantime, no review.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala73
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala21
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala1
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineReader.scala5
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Line.scala108
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) }
+ }
+ }
+ }
+}