summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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) }
+ }
+ }
+ }
+}