|
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 = |
|
43 List(File.standard_path(Path.explode("~~/lib/scripts/process")), "group", "-", "no_script") |
|
44 private val proc = Isabelle_System.execute_env(cwd, env, redirect, (params ::: args.toList):_*) |
|
45 |
|
46 |
|
47 // channels |
|
48 |
|
49 val stdin: BufferedWriter = |
|
50 new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset)) |
|
51 |
|
52 val stdout: BufferedReader = |
|
53 new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset)) |
|
54 |
|
55 val stderr: BufferedReader = |
|
56 new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset)) |
|
57 |
|
58 |
|
59 // signals |
|
60 |
|
61 private val pid = stdout.readLine |
|
62 |
|
63 private def kill_cmd(signal: String): Int = |
|
64 Isabelle_System.execute(true, "/usr/bin/env", "bash", "-c", "kill -" + signal + " -" + pid) |
|
65 .waitFor |
|
66 |
|
67 private def kill(signal: String): Boolean = |
|
68 Exn.Interrupt.postpone { kill_cmd(signal); kill_cmd("0") == 0 } getOrElse true |
|
69 |
|
70 private def multi_kill(signal: String): Boolean = |
|
71 { |
|
72 var running = true |
|
73 var count = 10 |
|
74 while (running && count > 0) { |
|
75 if (kill(signal)) { |
|
76 Exn.Interrupt.postpone { |
|
77 Thread.sleep(100) |
|
78 count -= 1 |
|
79 } |
|
80 } |
|
81 else running = false |
|
82 } |
|
83 running |
|
84 } |
|
85 |
|
86 def interrupt() { multi_kill("INT") } |
|
87 def terminate() { multi_kill("INT") && multi_kill("TERM") && kill("KILL"); proc.destroy } |
|
88 |
|
89 |
|
90 // JVM shutdown hook |
|
91 |
|
92 private val shutdown_hook = new Thread { override def run = terminate() } |
|
93 |
|
94 try { Runtime.getRuntime.addShutdownHook(shutdown_hook) } |
|
95 catch { case _: IllegalStateException => } |
|
96 |
|
97 private def cleanup() = |
|
98 try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) } |
|
99 catch { case _: IllegalStateException => } |
|
100 |
|
101 |
|
102 /* result */ |
|
103 |
|
104 def join: Int = { val rc = proc.waitFor; cleanup(); rc } |
|
105 } |
|
106 } |