blob: 2dc23aa38ae56eebb91003202ab0ad002db452e5 (
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-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) }
}
}
}
}
|