| author | wenzelm | 
| Wed, 05 Jul 2023 14:33:32 +0200 | |
| changeset 78254 | 50a949d316d3 | 
| parent 76146 | a64f3496d93a | 
| child 78910 | d305af09fbad | 
| permissions | -rw-r--r-- | 
| 62584 | 1 | /* Title: Pure/System/bash.scala | 
| 60991 | 2 | Author: Makarius | 
| 3 | ||
| 74158 | 4 | Support for GNU bash: portable external processes with propagation of | 
| 5 | interrupts. | |
| 60991 | 6 | */ | 
| 7 | ||
| 8 | package isabelle | |
| 9 | ||
| 10 | ||
| 74273 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 11 | import java.util.{List => JList, Map => JMap}
 | 
| 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 12 | import java.io.{BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter,
 | 
| 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 13 | File => JFile, IOException} | 
| 71706 | 14 | import scala.annotation.tailrec | 
| 73602 
37243ad3ecb6
fast approximation of test for process group (NB: initial process might already be terminated, while background processes are still running);
 wenzelm parents: 
73601diff
changeset | 15 | import scala.jdk.OptionConverters._ | 
| 71706 | 16 | |
| 60991 | 17 | |
| 75393 | 18 | object Bash {
 | 
| 64304 | 19 | /* concrete syntax */ | 
| 20 | ||
| 75393 | 21 |   private def bash_chr(c: Byte): String = {
 | 
| 64304 | 22 | val ch = c.toChar | 
| 23 |     ch match {
 | |
| 24 | case '\t' => "$'\\t'" | |
| 25 | case '\n' => "$'\\n'" | |
| 26 | case '\f' => "$'\\f'" | |
| 27 | case '\r' => "$'\\r'" | |
| 28 | case _ => | |
| 67200 | 29 | if (Symbol.is_ascii_letter(ch) || Symbol.is_ascii_digit(ch) || "+,-./:_".contains(ch)) | 
| 64304 | 30 | Symbol.ascii(ch) | 
| 31 | else if (c < 0) "$'\\x" + Integer.toHexString(256 + c) + "'" | |
| 32 | else if (c < 16) "$'\\x0" + Integer.toHexString(c) + "'" | |
| 33 | else if (c < 32 || c >= 127) "$'\\x" + Integer.toHexString(c) + "'" | |
| 34 | else "\\" + ch | |
| 35 | } | |
| 36 | } | |
| 37 | ||
| 38 | def string(s: String): String = | |
| 39 | if (s == "") "\"\"" | |
| 71601 | 40 | else UTF8.bytes(s).iterator.map(bash_chr).mkString | 
| 64304 | 41 | |
| 42 | def strings(ss: List[String]): String = | |
| 71601 | 43 |     ss.iterator.map(Bash.string).mkString(" ")
 | 
| 64304 | 44 | |
| 45 | ||
| 46 | /* process and result */ | |
| 47 | ||
| 74212 | 48 | private def make_description(description: String): String = | 
| 49 | proper_string(description) getOrElse "bash_process" | |
| 50 | ||
| 72598 | 51 | type Watchdog = (Time, Process => Boolean) | 
| 52 | ||
| 62545 
8ebffdaf2ce2
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
 wenzelm parents: 
62543diff
changeset | 53 | def process(script: String, | 
| 74212 | 54 | description: String = "", | 
| 62573 | 55 | cwd: JFile = null, | 
| 73897 | 56 | env: JMap[String, String] = Isabelle_System.settings(), | 
| 62602 | 57 | redirect: Boolean = false, | 
| 58 | cleanup: () => Unit = () => ()): Process = | |
| 74212 | 59 | new Process(script, description, cwd, env, redirect, cleanup) | 
| 60991 | 60 | |
| 63996 | 61 | class Process private[Bash]( | 
| 75393 | 62 | script: String, | 
| 63 | description: String, | |
| 64 | cwd: JFile, | |
| 65 | env: JMap[String, String], | |
| 66 | redirect: Boolean, | |
| 67 | cleanup: () => Unit | |
| 68 |   ) {
 | |
| 74212 | 69 | override def toString: String = make_description(description) | 
| 70 | ||
| 62604 | 71 |     private val timing_file = Isabelle_System.tmp_file("bash_timing")
 | 
| 62569 | 72 | private val timing = Synchronized[Option[Timing]](None) | 
| 65317 | 73 | def get_timing: Timing = timing.value getOrElse Timing.zero | 
| 62569 | 74 | |
| 73603 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 75 | private val winpid_file: Option[JFile] = | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 76 |       if (Platform.is_windows) Some(Isabelle_System.tmp_file("bash_winpid")) else None
 | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 77 | private val winpid_script = | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 78 |       winpid_file match {
 | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 79 | case None => script | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 80 | case Some(file) => | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 81 | "read < /proc/self/winpid > /dev/null 2> /dev/null\n" + | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 82 | """echo -n "$REPLY" > """ + File.bash_path(file) + "\n\n" + script | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 83 | } | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 84 | |
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 85 |     private val script_file: JFile = Isabelle_System.tmp_file("bash_script")
 | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 86 | File.write(script_file, winpid_script) | 
| 62545 
8ebffdaf2ce2
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
 wenzelm parents: 
62543diff
changeset | 87 | |
| 62296 | 88 | private val proc = | 
| 73906 | 89 | isabelle.setup.Environment.process_builder( | 
| 73895 | 90 |         JList.of(File.platform_path(Path.variable("ISABELLE_BASH_PROCESS")),
 | 
| 62610 
4c89504c76fb
more uniform signature for various process invocations;
 wenzelm parents: 
62604diff
changeset | 91 | File.standard_path(timing_file), "bash", File.standard_path(script_file)), | 
| 73906 | 92 | cwd, env, redirect).start() | 
| 60991 | 93 | |
| 94 | ||
| 95 | // channels | |
| 96 | ||
| 97 | val stdin: BufferedWriter = | |
| 98 | new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset)) | |
| 99 | ||
| 100 | val stdout: BufferedReader = | |
| 101 | new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset)) | |
| 102 | ||
| 103 | val stderr: BufferedReader = | |
| 104 | new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset)) | |
| 105 | ||
| 106 | ||
| 107 | // signals | |
| 108 | ||
| 73601 | 109 | private val group_pid = stdout.readLine | 
| 60991 | 110 | |
| 73603 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 111 | private def process_alive(pid: String): Boolean = | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 112 |       (for {
 | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 113 | p <- Value.Long.unapply(pid) | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 114 | handle <- ProcessHandle.of(p).toScala | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 115 | } yield handle.isAlive) getOrElse false | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 116 | |
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 117 | private def root_process_alive(): Boolean = | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 118 |       winpid_file match {
 | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 119 | case None => process_alive(group_pid) | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 120 | case Some(file) => | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 121 | file.exists() && process_alive(Library.trim_line(File.read(file))) | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 122 | } | 
| 73602 
37243ad3ecb6
fast approximation of test for process group (NB: initial process might already be terminated, while background processes are still running);
 wenzelm parents: 
73601diff
changeset | 123 | |
| 75393 | 124 |     @tailrec private def signal(s: String, count: Int = 1): Boolean = {
 | 
| 125 |       count <= 0 || {
 | |
| 73911 | 126 | isabelle.setup.Environment.kill_process(group_pid, s) | 
| 127 | val running = | |
| 128 | root_process_alive() || | |
| 129 | isabelle.setup.Environment.kill_process(group_pid, "0") | |
| 71707 | 130 |         if (running) {
 | 
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73604diff
changeset | 131 | Time.seconds(0.1).sleep() | 
| 73601 | 132 | signal(s, count - 1) | 
| 60991 | 133 | } | 
| 71706 | 134 | else false | 
| 60991 | 135 | } | 
| 136 | } | |
| 137 | ||
| 75393 | 138 |     def terminate(): Unit = Isabelle_Thread.try_uninterruptible {
 | 
| 73601 | 139 |       signal("INT", count = 7) && signal("TERM", count = 3) && signal("KILL")
 | 
| 73367 | 140 | proc.destroy() | 
| 62602 | 141 | do_cleanup() | 
| 62574 | 142 | } | 
| 60991 | 143 | |
| 75393 | 144 |     def interrupt(): Unit = Isabelle_Thread.try_uninterruptible {
 | 
| 73911 | 145 | isabelle.setup.Environment.kill_process(group_pid, "INT") | 
| 71705 | 146 | } | 
| 147 | ||
| 60991 | 148 | |
| 62574 | 149 | // cleanup | 
| 62545 
8ebffdaf2ce2
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
 wenzelm parents: 
62543diff
changeset | 150 | |
| 76146 | 151 | private val shutdown_hook = | 
| 152 |       Isabelle_System.create_shutdown_hook { terminate() }
 | |
| 153 | ||
| 75393 | 154 |     private def do_cleanup(): Unit = {
 | 
| 76146 | 155 | Isabelle_System.remove_shutdown_hook(shutdown_hook) | 
| 60991 | 156 | |
| 73603 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 157 | script_file.delete() | 
| 
342362c9496c
clarified check of root process on Windows (NB: the winpid is less stable than the Cygwin/Posix pid, so it needs to be "patched" into the the bash script, instead of bash_process.c);
 wenzelm parents: 
73602diff
changeset | 158 | winpid_file.foreach(_.delete()) | 
| 60991 | 159 | |
| 62569 | 160 |       timing.change {
 | 
| 161 | case None => | |
| 62574 | 162 |           if (timing_file.isFile) {
 | 
| 163 | val t = | |
| 164 |               Word.explode(File.read(timing_file)) match {
 | |
| 63805 | 165 | case List(Value.Long(elapsed), Value.Long(cpu)) => | 
| 62574 | 166 | Timing(Time.ms(elapsed), Time.ms(cpu), Time.zero) | 
| 167 | case _ => Timing.zero | |
| 168 | } | |
| 169 | timing_file.delete | |
| 170 | Some(t) | |
| 171 | } | |
| 172 | else Some(Timing.zero) | |
| 62569 | 173 | case some => some | 
| 174 | } | |
| 62602 | 175 | |
| 176 | cleanup() | |
| 62574 | 177 | } | 
| 62569 | 178 | |
| 62574 | 179 | |
| 180 | // join | |
| 181 | ||
| 75393 | 182 |     def join(): Int = {
 | 
| 74141 | 183 | val rc = proc.waitFor() | 
| 62602 | 184 | do_cleanup() | 
| 62545 
8ebffdaf2ce2
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
 wenzelm parents: 
62543diff
changeset | 185 | rc | 
| 
8ebffdaf2ce2
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
 wenzelm parents: 
62543diff
changeset | 186 | } | 
| 62543 | 187 | |
| 188 | ||
| 62574 | 189 | // result | 
| 62543 | 190 | |
| 191 | def result( | |
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 192 | input: String = "", | 
| 62543 | 193 | progress_stdout: String => Unit = (_: String) => (), | 
| 194 | progress_stderr: String => Unit = (_: String) => (), | |
| 72598 | 195 | watchdog: Option[Watchdog] = None, | 
| 75393 | 196 | strict: Boolean = true | 
| 197 |     ): Process_Result = {
 | |
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 198 | val in = | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 199 | if (input.isEmpty) Future.value(stdin.close()) | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 200 |         else Future.thread("bash_stdin") { stdin.write(input); stdin.flush(); stdin.close(); }
 | 
| 62543 | 201 | val out_lines = | 
| 72105 | 202 |         Future.thread("bash_stdout") { File.read_lines(stdout, progress_stdout) }
 | 
| 62543 | 203 | val err_lines = | 
| 72105 | 204 |         Future.thread("bash_stderr") { File.read_lines(stderr, progress_stderr) }
 | 
| 62543 | 205 | |
| 72598 | 206 | val watchdog_thread = | 
| 207 | for ((time, check) <- watchdog) | |
| 208 |         yield {
 | |
| 209 |           Future.thread("bash_watchdog") {
 | |
| 210 |             while (proc.isAlive) {
 | |
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73604diff
changeset | 211 | time.sleep() | 
| 72598 | 212 | if (check(this)) interrupt() | 
| 213 | } | |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 62543 | 217 | val rc = | 
| 74141 | 218 |         try { join() }
 | 
| 74306 | 219 |         catch { case Exn.Interrupt() => terminate(); Process_Result.RC.interrupt }
 | 
| 72598 | 220 | |
| 73367 | 221 | watchdog_thread.foreach(_.cancel()) | 
| 72598 | 222 | |
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 223 | in.join | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 224 | out_lines.join | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 225 | err_lines.join | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 226 | |
| 74306 | 227 | if (strict && rc == Process_Result.RC.interrupt) throw Exn.Interrupt() | 
| 62543 | 228 | |
| 73134 
8a8ffe78eee7
clarified return code: re-use SIGALRM for soft timeout;
 wenzelm parents: 
72598diff
changeset | 229 | Process_Result(rc, out_lines.join, err_lines.join, get_timing) | 
| 62543 | 230 | } | 
| 60991 | 231 | } | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 232 | |
| 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 233 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 234 | /* server */ | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 235 | |
| 75393 | 236 |   object Server {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 237 | // input messages | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 238 | private val RUN = "run" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 239 | private val KILL = "kill" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 240 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 241 | // output messages | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 242 | private val UUID = "uuid" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 243 | private val INTERRUPT = "interrupt" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 244 | private val FAILURE = "failure" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 245 | private val RESULT = "result" | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 246 | |
| 75393 | 247 |     def start(port: Int = 0, debugging: => Boolean = false): Server = {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 248 | val server = new Server(port, debugging) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 249 | server.start() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 250 | server | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 251 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 252 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 253 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 254 | class Server private(port: Int, debugging: => Boolean) | 
| 75393 | 255 |   extends isabelle.Server.Handler(port) {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 256 | server => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 257 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 258 | private val _processes = Synchronized(Map.empty[UUID.T, Bash.Process]) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 259 | |
| 75393 | 260 |     override def stop(): Unit = {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 261 | for ((_, process) <- _processes.value) process.terminate() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 262 | super.stop() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 263 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 264 | |
| 75393 | 265 |     override def handle(connection: isabelle.Server.Connection): Unit = {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 266 | def reply(chunks: List[String]): Unit = | 
| 74273 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 267 |         try { connection.write_byte_message(chunks.map(Bytes.apply)) }
 | 
| 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 268 |         catch { case _: IOException => }
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 269 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 270 | def reply_failure(exn: Throwable): Unit = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 271 | reply( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 272 | if (Exn.is_interrupt(exn)) List(Server.INTERRUPT) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 273 | else List(Server.FAILURE, Exn.message(exn))) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 274 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 275 | def reply_result(result: Process_Result): Unit = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 276 | reply( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 277 | Server.RESULT :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 278 | result.rc.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 279 | result.timing.elapsed.ms.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 280 | result.timing.cpu.ms.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 281 | result.out_lines.length.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 282 | result.out_lines ::: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 283 | result.err_lines) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 284 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 285 |       connection.read_byte_message().map(_.map(_.text)) match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 286 | case None => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 287 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 288 | case Some(List(Server.KILL, UUID(uuid))) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 289 |           if (debugging) Output.writeln("kill " + uuid)
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 290 | _processes.value.get(uuid).foreach(_.terminate()) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 291 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 292 | case Some(List(Server.RUN, script, input, cwd, putenv, | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 293 | Value.Boolean(redirect), Value.Seconds(timeout), description)) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 294 | val uuid = UUID.random() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 295 | |
| 74212 | 296 | val descr = make_description(description) | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 297 |           if (debugging) {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 298 | Output.writeln( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 299 | "start " + quote(descr) + " (uuid=" + uuid + ", timeout=" + timeout.seconds + ")") | 
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 300 | } | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 301 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 302 |           Exn.capture {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 303 | Bash.process(script, | 
| 74212 | 304 | description = description, | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 305 | cwd = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 306 |                 XML.Decode.option(XML.Decode.string)(YXML.parse_body(cwd)) match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 307 | case None => null | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 308 | case Some(s) => Path.explode(s).file | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 309 | }, | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 310 | env = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 311 | Isabelle_System.settings( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 312 | XML.Decode.list(XML.Decode.pair(XML.Decode.string, XML.Decode.string))( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 313 | YXML.parse_body(putenv))), | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 314 | redirect= redirect) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 315 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 316 |           match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 317 | case Exn.Exn(exn) => reply_failure(exn) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 318 | case Exn.Res(process) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 319 | _processes.change(processes => processes + (uuid -> process)) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 320 | reply(List(Server.UUID, uuid.toString)) | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 321 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 322 |               Isabelle_Thread.fork(name = "bash_process") {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 323 | @volatile var is_timeout = false | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 324 | val watchdog: Option[Watchdog] = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 325 |                   if (timeout.is_zero) None else Some((timeout, _ => { is_timeout = true; true }))
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 326 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 327 |                 Exn.capture { process.result(input = input, watchdog = watchdog, strict = false) }
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 328 |                 match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 329 | case Exn.Exn(exn) => reply_failure(exn) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 330 | case Exn.Res(res0) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 331 | val res = if (!res0.ok && is_timeout) res0.timeout_rc else res0 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 332 |                     if (debugging) {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 333 | Output.writeln( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 334 | "stop " + quote(descr) + " (uuid=" + uuid + ", return_code=" + res.rc + ")") | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 335 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 336 | reply_result(res) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 337 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 338 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 339 | _processes.change(provers => provers - uuid) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 340 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 341 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 342 | connection.await_close() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 343 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 344 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 345 |         case Some(_) => reply_failure(ERROR("Bad protocol message"))
 | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 346 | } | 
| 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 347 | } | 
| 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 348 | } | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 349 | |
| 75393 | 350 |   class Handler extends Session.Protocol_Handler {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 351 | private var server: Server = null | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 352 | |
| 75393 | 353 |     override def init(session: Session): Unit = {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 354 | exit() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 355 |       server = Server.start(debugging = session.session_options.bool("bash_process_debugging"))
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 356 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 357 | |
| 75393 | 358 |     override def exit(): Unit = {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 359 |       if (server != null) {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 360 | server.stop() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 361 | server = null | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 362 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 363 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 364 | |
| 75393 | 365 |     override def prover_options(options: Options): Options = {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 366 | val address = if (server == null) "" else server.address | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 367 | val password = if (server == null) "" else server.password | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 368 | options + | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 369 |         ("bash_process_address=" + address) +
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 370 |         ("bash_process_password=" + password)
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 371 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 372 | } | 
| 60991 | 373 | } |