diff options
author | Paul Phillips <paulp@improving.org> | 2011-05-31 05:08:25 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-05-31 05:08:25 +0000 |
commit | edad717cc1934d80dc0b6a9af528eed8ef4b30b6 (patch) | |
tree | abd8506f32326d513177569192c0ae5d546cae8a /src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala | |
parent | 35f7c2bde563b501e9661aace1e1610990b51544 (diff) | |
download | scala-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.scala | 124 |
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() + } + } +} |