summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interpreter/Line.scala
blob: deaeb913d2d7b74463493c8b8a4e06ab9ee00598 (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
/* NSC -- new Scala compiler
 * Copyright 2005-2011 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 {
    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) }
      }
    }
  }
}