summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-05-31 05:08:25 +0000
committerPaul Phillips <paulp@improving.org>2011-05-31 05:08:25 +0000
commitedad717cc1934d80dc0b6a9af528eed8ef4b30b6 (patch)
treeabd8506f32326d513177569192c0ae5d546cae8a /src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala
parent35f7c2bde563b501e9661aace1e1610990b51544 (diff)
downloadscala-edad717cc1934d80dc0b6a9af528eed8ef4b30b6.tar.gz
scala-edad717cc1934d80dc0b6a9af528eed8ef4b30b6.tar.bz2
scala-edad717cc1934d80dc0b6a9af528eed8ef4b30b6.zip
Working on the finer points of the Total Repl E...
Working on the finer points of the Total Repl Experience. Some of what is in this patch: -- Instantaneous repl startup. Closes #4561. -- SIGINT handler installed last. If you've been annoyed at the repl being difficult to interrupt during its initialization, this one is especially for you. -- Started abstracting out some bits which would go into an API and reconfiguring the repl in those terms. For a good time, call S-C-A-L-A -Dscala.repl.power. No review.
Diffstat (limited to 'src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala')
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala124
1 files changed, 124 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala
new file mode 100644
index 0000000000..dbd4d77a44
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala
@@ -0,0 +1,124 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2011 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import scala.tools.util.SignalManager
+import scala.util.control.Exception.ignoring
+
+/**
+ * Machinery for the asynchronous initialization of the repl.
+ */
+trait ILoopInit {
+ self: ILoop =>
+
+ /** Print a welcome message */
+ def printWelcome() {
+ import Properties._
+ val welcomeMsg =
+ """|Welcome to Scala %s (%s, Java %s).
+ |Type in expressions to have them evaluated.
+ |Type :help for more information.""" .
+ stripMargin.format(versionString, javaVmName, javaVersion)
+ echo(welcomeMsg)
+ if (isReplDebug || isReplPower)
+ echo("[info] started at " + new java.util.Date)
+ }
+
+ private def asyncMessage(msg: String) {
+ if (isReplDebug || isReplPower)
+ echoAndRefresh(msg)
+ }
+
+ /** Try to install sigint handler: ignore failure. Signal handler
+ * will interrupt current line execution if any is in progress.
+ *
+ * Attempting to protect the repl from accidental exit, we only honor
+ * a single ctrl-C if the current buffer is empty: otherwise we look
+ * for a second one within a short time.
+ */
+ protected def installSigIntHandler() {
+ def onExit() {
+ Console.println("") // avoiding "shell prompt in middle of line" syndrome
+ sys.exit(1)
+ }
+ ignoring(classOf[Exception]) {
+ SignalManager("INT") = {
+ if (intp == null)
+ onExit()
+ else if (intp.lineManager.running)
+ intp.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()) // and restore original handler if they don't
+ }
+ else onExit()
+ }
+ }
+ }
+
+ private val initLock = new java.util.concurrent.locks.ReentrantLock()
+ private def withLock[T](body: => T): T = {
+ initLock.lock()
+ try body
+ finally initLock.unlock()
+ }
+ // a condition used to ensure serial access to the compiler.
+ @volatile private var initIsComplete = false
+ private val initCompilerCondition = initLock.newCondition() // signal the compiler is initialized
+ private val initLoopCondition = initLock.newCondition() // signal the whole repl is initialized
+ private val initStart = System.nanoTime
+ private def elapsed() = "%.3f".format((System.nanoTime - initStart).toDouble / 1000000000L)
+
+ // Receives a single message once the interpreter is initialized.
+ private val initializedReactor = io.spawn {
+ withLock(initCompilerCondition.await())
+ asyncMessage("[info] compiler init time: " + elapsed() + " s.")
+ postInitialization()
+ }
+
+ // the method to be called when the interpreter is initialized.
+ // Very important this method does nothing synchronous (i.e. do
+ // not try to use the interpreter) because until it returns, the
+ // repl's lazy val `global` is still locked.
+ protected def initializedCallback() = withLock(initCompilerCondition.signal())
+
+ // called from main repl loop
+ protected def awaitInitialized() {
+ if (!initIsComplete)
+ withLock { while (!initIsComplete) initLoopCondition.await() }
+ }
+ // called once after init condition is signalled
+ protected def postInitialization() {
+ addThunk(intp.setContextClassLoader())
+ // do this last to avoid annoying uninterruptible startups
+ addThunk(installSigIntHandler())
+ if (isReplPower)
+ addThunk(enablePowerMode())
+
+ runThunks()
+ initIsComplete = true
+ asyncMessage("[info] total init time: " + elapsed() + " s.")
+ withLock(initLoopCondition.signal())
+ }
+ // code to be executed only after the interpreter is initialized
+ // and the lazy val `global` can be accessed without risk of deadlock.
+ private var pendingThunks: List[() => Unit] = Nil
+ protected def addThunk(body: => Unit) = synchronized {
+ pendingThunks :+= (() => body)
+ }
+ protected def runThunks(): Unit = synchronized {
+ if (pendingThunks.nonEmpty)
+ repldbg("Clearing " + pendingThunks.size + " thunks.")
+
+ while (pendingThunks.nonEmpty) {
+ val thunk = pendingThunks.head
+ pendingThunks = pendingThunks.tail
+ thunk()
+ }
+ }
+}