60991
|
1 |
/* Title: Pure/Concurrent/bash.scala
|
|
2 |
Author: Makarius
|
|
3 |
|
|
4 |
GNU bash processes, with propagation of interrupts.
|
|
5 |
*/
|
|
6 |
|
|
7 |
package isabelle
|
|
8 |
|
|
9 |
|
|
10 |
import java.io.{File => JFile, BufferedReader, InputStreamReader,
|
|
11 |
BufferedWriter, OutputStreamWriter}
|
|
12 |
|
|
13 |
|
|
14 |
object Bash
|
|
15 |
{
|
|
16 |
/** result **/
|
|
17 |
|
|
18 |
final case class Result(out_lines: List[String], err_lines: List[String], rc: Int)
|
|
19 |
{
|
|
20 |
def out: String = cat_lines(out_lines)
|
|
21 |
def err: String = cat_lines(err_lines)
|
|
22 |
def add_err(s: String): Result = copy(err_lines = err_lines ::: List(s))
|
|
23 |
def set_rc(i: Int): Result = copy(rc = i)
|
|
24 |
|
|
25 |
def check_error: Result =
|
|
26 |
if (rc == Exn.Interrupt.return_code) throw Exn.Interrupt()
|
|
27 |
else if (rc != 0) error(err)
|
|
28 |
else this
|
|
29 |
}
|
|
30 |
|
|
31 |
|
|
32 |
|
|
33 |
/** process **/
|
|
34 |
|
|
35 |
def process(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*): Process =
|
|
36 |
new Process(cwd, env, redirect, args:_*)
|
|
37 |
|
|
38 |
class Process private [Bash](
|
|
39 |
cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*)
|
|
40 |
extends Prover.System_Process
|
|
41 |
{
|
|
42 |
private val params =
|
62296
|
43 |
List(Isabelle_System.getenv_strict("ISABELLE_BASH_PROCESS"), "-", "bash")
|
|
44 |
private val proc =
|
|
45 |
Isabelle_System.execute_env(cwd, env, redirect, (params ::: args.toList):_*)
|
60991
|
46 |
|
|
47 |
|
|
48 |
// channels
|
|
49 |
|
|
50 |
val stdin: BufferedWriter =
|
|
51 |
new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset))
|
|
52 |
|
|
53 |
val stdout: BufferedReader =
|
|
54 |
new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset))
|
|
55 |
|
|
56 |
val stderr: BufferedReader =
|
|
57 |
new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset))
|
|
58 |
|
|
59 |
|
|
60 |
// signals
|
|
61 |
|
|
62 |
private val pid = stdout.readLine
|
|
63 |
|
|
64 |
private def kill(signal: String): Boolean =
|
61025
|
65 |
Exn.Interrupt.postpone {
|
|
66 |
Isabelle_System.kill(signal, pid)
|
|
67 |
Isabelle_System.kill("0", pid)._2 == 0 } getOrElse true
|
60991
|
68 |
|
|
69 |
private def multi_kill(signal: String): Boolean =
|
|
70 |
{
|
|
71 |
var running = true
|
|
72 |
var count = 10
|
|
73 |
while (running && count > 0) {
|
|
74 |
if (kill(signal)) {
|
|
75 |
Exn.Interrupt.postpone {
|
|
76 |
Thread.sleep(100)
|
|
77 |
count -= 1
|
|
78 |
}
|
|
79 |
}
|
|
80 |
else running = false
|
|
81 |
}
|
|
82 |
running
|
|
83 |
}
|
|
84 |
|
|
85 |
def interrupt() { multi_kill("INT") }
|
|
86 |
def terminate() { multi_kill("INT") && multi_kill("TERM") && kill("KILL"); proc.destroy }
|
|
87 |
|
|
88 |
|
|
89 |
// JVM shutdown hook
|
|
90 |
|
|
91 |
private val shutdown_hook = new Thread { override def run = terminate() }
|
|
92 |
|
|
93 |
try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
|
|
94 |
catch { case _: IllegalStateException => }
|
|
95 |
|
|
96 |
private def cleanup() =
|
|
97 |
try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
|
|
98 |
catch { case _: IllegalStateException => }
|
|
99 |
|
|
100 |
|
|
101 |
/* result */
|
|
102 |
|
|
103 |
def join: Int = { val rc = proc.waitFor; cleanup(); rc }
|
|
104 |
}
|
|
105 |
}
|