summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala
blob: 2f02748e8f58b63525f195f1dd8892326c09b681 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/* NSC -- new Scala compiler
 * Copyright 2005-2011 LAMP/EPFL
 * @author Paul Phillips
 */

package scala.tools.nsc
package interpreter

import util.Position
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)
    replinfo("[info] started at " + new java.util.Date)
  }

  protected def asyncMessage(msg: String) {
    if (isReplInfo || 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 || intp.lineManager == 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()
      }
    }
  }
  protected def removeSigIntHandler() {
    squashAndLog("removeSigIntHandler")(SignalManager("INT") = null)
  }

  private val initLock = new java.util.concurrent.locks.ReentrantLock()
  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 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 def elapsed() = "%.3f".format((System.nanoTime - initStart).toDouble / 1000000000L)

  // 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())

  // Spins off a thread which awaits a single message once the interpreter
  // has been initialized.
  protected def createAsyncListener() = {
    io.spawn {
      withLock(initCompilerCondition.await())
      asyncMessage("[info] compiler init time: " + elapsed() + " s.")
      postInitialization()
    }
  }

  // called from main repl loop
  protected def awaitInitialized() {
    if (!initIsComplete)
      withLock { while (!initIsComplete) initLoopCondition.await() }
  }
  // private def warningsThunks = List(
  //   () => intp.bind("lastWarnings", "" + manifest[List[(Position, String)]], intp.lastWarnings _),
  // )

  protected def postInitThunks = List[Option[() => Unit]](
    Some(intp.setContextClassLoader _),
    if (isReplPower) Some(() => enablePowerMode(true)) else None,
    // do this last to avoid annoying uninterruptible startups
    Some(installSigIntHandler _)
  ).flatten
  // ++ (
  //   warningsThunks
  // )
  // called once after init condition is signalled
  protected def postInitialization() {
    postInitThunks foreach (f => addThunk(f()))
    runThunks()
    initIsComplete = true

    if (isAsync) {
      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()
    }
  }
}