| author | wenzelm | 
| Fri, 24 Sep 2021 22:23:26 +0200 | |
| changeset 74362 | 0135a0c77b64 | 
| parent 74306 | a117c076aa22 | 
| child 75393 | 87ebf5a50283 | 
| 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 | |
| 18 | object Bash | |
| 19 | {
 | |
| 64304 | 20 | /* concrete syntax */ | 
| 21 | ||
| 22 | private def bash_chr(c: Byte): String = | |
| 23 |   {
 | |
| 24 | val ch = c.toChar | |
| 25 |     ch match {
 | |
| 26 | case '\t' => "$'\\t'" | |
| 27 | case '\n' => "$'\\n'" | |
| 28 | case '\f' => "$'\\f'" | |
| 29 | case '\r' => "$'\\r'" | |
| 30 | case _ => | |
| 67200 | 31 | if (Symbol.is_ascii_letter(ch) || Symbol.is_ascii_digit(ch) || "+,-./:_".contains(ch)) | 
| 64304 | 32 | Symbol.ascii(ch) | 
| 33 | else if (c < 0) "$'\\x" + Integer.toHexString(256 + c) + "'" | |
| 34 | else if (c < 16) "$'\\x0" + Integer.toHexString(c) + "'" | |
| 35 | else if (c < 32 || c >= 127) "$'\\x" + Integer.toHexString(c) + "'" | |
| 36 | else "\\" + ch | |
| 37 | } | |
| 38 | } | |
| 39 | ||
| 40 | def string(s: String): String = | |
| 41 | if (s == "") "\"\"" | |
| 71601 | 42 | else UTF8.bytes(s).iterator.map(bash_chr).mkString | 
| 64304 | 43 | |
| 44 | def strings(ss: List[String]): String = | |
| 71601 | 45 |     ss.iterator.map(Bash.string).mkString(" ")
 | 
| 64304 | 46 | |
| 47 | ||
| 48 | /* process and result */ | |
| 49 | ||
| 74212 | 50 | private def make_description(description: String): String = | 
| 51 | proper_string(description) getOrElse "bash_process" | |
| 52 | ||
| 72598 | 53 | type Watchdog = (Time, Process => Boolean) | 
| 54 | ||
| 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 | 55 | def process(script: String, | 
| 74212 | 56 | description: String = "", | 
| 62573 | 57 | cwd: JFile = null, | 
| 73897 | 58 | env: JMap[String, String] = Isabelle_System.settings(), | 
| 62602 | 59 | redirect: Boolean = false, | 
| 60 | cleanup: () => Unit = () => ()): Process = | |
| 74212 | 61 | new Process(script, description, cwd, env, redirect, cleanup) | 
| 60991 | 62 | |
| 63996 | 63 | class Process private[Bash]( | 
| 62602 | 64 | script: String, | 
| 74212 | 65 | description: String, | 
| 62602 | 66 | cwd: JFile, | 
| 73897 | 67 | env: JMap[String, String], | 
| 62602 | 68 | redirect: Boolean, | 
| 69 | cleanup: () => Unit) | |
| 60991 | 70 |   {
 | 
| 74212 | 71 | override def toString: String = make_description(description) | 
| 72 | ||
| 62604 | 73 |     private val timing_file = Isabelle_System.tmp_file("bash_timing")
 | 
| 62569 | 74 | private val timing = Synchronized[Option[Timing]](None) | 
| 65317 | 75 | def get_timing: Timing = timing.value getOrElse Timing.zero | 
| 62569 | 76 | |
| 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 | 77 | 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 | 78 |       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 | 79 | 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 | 80 |       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 | 81 | 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 | 82 | 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 | 83 | "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 | 84 | """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 | 85 | } | 
| 
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 | |
| 
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 | 87 |     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 | 88 | 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 | 89 | |
| 62296 | 90 | private val proc = | 
| 73906 | 91 | isabelle.setup.Environment.process_builder( | 
| 73895 | 92 |         JList.of(File.platform_path(Path.variable("ISABELLE_BASH_PROCESS")),
 | 
| 62610 
4c89504c76fb
more uniform signature for various process invocations;
 wenzelm parents: 
62604diff
changeset | 93 | File.standard_path(timing_file), "bash", File.standard_path(script_file)), | 
| 73906 | 94 | cwd, env, redirect).start() | 
| 60991 | 95 | |
| 96 | ||
| 97 | // channels | |
| 98 | ||
| 99 | val stdin: BufferedWriter = | |
| 100 | new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset)) | |
| 101 | ||
| 102 | val stdout: BufferedReader = | |
| 103 | new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset)) | |
| 104 | ||
| 105 | val stderr: BufferedReader = | |
| 106 | new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset)) | |
| 107 | ||
| 108 | ||
| 109 | // signals | |
| 110 | ||
| 73601 | 111 | private val group_pid = stdout.readLine | 
| 60991 | 112 | |
| 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 | 113 | 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 | 114 |       (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 | 115 | 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 | 116 | 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 | 117 | } 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 | 118 | |
| 
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 | 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 | 120 |       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 | 121 | 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 | 122 | 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 | 123 | 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 | 124 | } | 
| 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 | 125 | |
| 73601 | 126 | @tailrec private def signal(s: String, count: Int = 1): Boolean = | 
| 60991 | 127 |     {
 | 
| 71706 | 128 | count <= 0 || | 
| 129 |       {
 | |
| 73911 | 130 | isabelle.setup.Environment.kill_process(group_pid, s) | 
| 131 | val running = | |
| 132 | root_process_alive() || | |
| 133 | isabelle.setup.Environment.kill_process(group_pid, "0") | |
| 71707 | 134 |         if (running) {
 | 
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73604diff
changeset | 135 | Time.seconds(0.1).sleep() | 
| 73601 | 136 | signal(s, count - 1) | 
| 60991 | 137 | } | 
| 71706 | 138 | else false | 
| 60991 | 139 | } | 
| 140 | } | |
| 141 | ||
| 71712 
c6b7f4da67b3
more robust kill: not always running on Isabelle_Thread (e.g. POSIX_Interrupt handler);
 wenzelm parents: 
71708diff
changeset | 142 | def terminate(): Unit = Isabelle_Thread.try_uninterruptible | 
| 62574 | 143 |     {
 | 
| 73601 | 144 |       signal("INT", count = 7) && signal("TERM", count = 3) && signal("KILL")
 | 
| 73367 | 145 | proc.destroy() | 
| 62602 | 146 | do_cleanup() | 
| 62574 | 147 | } | 
| 60991 | 148 | |
| 71712 
c6b7f4da67b3
more robust kill: not always running on Isabelle_Thread (e.g. POSIX_Interrupt handler);
 wenzelm parents: 
71708diff
changeset | 149 | def interrupt(): Unit = Isabelle_Thread.try_uninterruptible | 
| 71705 | 150 |     {
 | 
| 73911 | 151 | isabelle.setup.Environment.kill_process(group_pid, "INT") | 
| 71705 | 152 | } | 
| 153 | ||
| 60991 | 154 | |
| 155 | // JVM shutdown hook | |
| 156 | ||
| 71712 
c6b7f4da67b3
more robust kill: not always running on Isabelle_Thread (e.g. POSIX_Interrupt handler);
 wenzelm parents: 
71708diff
changeset | 157 | private val shutdown_hook = Isabelle_Thread.create(() => terminate()) | 
| 60991 | 158 | |
| 159 |     try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
 | |
| 160 |     catch { case _: IllegalStateException => }
 | |
| 161 | ||
| 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 | 162 | |
| 62574 | 163 | // 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 | 164 | |
| 73340 | 165 | private def do_cleanup(): Unit = | 
| 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 | 166 |     {
 | 
| 60991 | 167 |       try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
 | 
| 168 |       catch { case _: IllegalStateException => }
 | |
| 169 | ||
| 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 | 170 | 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 | 171 | winpid_file.foreach(_.delete()) | 
| 60991 | 172 | |
| 62569 | 173 |       timing.change {
 | 
| 174 | case None => | |
| 62574 | 175 |           if (timing_file.isFile) {
 | 
| 176 | val t = | |
| 177 |               Word.explode(File.read(timing_file)) match {
 | |
| 63805 | 178 | case List(Value.Long(elapsed), Value.Long(cpu)) => | 
| 62574 | 179 | Timing(Time.ms(elapsed), Time.ms(cpu), Time.zero) | 
| 180 | case _ => Timing.zero | |
| 181 | } | |
| 182 | timing_file.delete | |
| 183 | Some(t) | |
| 184 | } | |
| 185 | else Some(Timing.zero) | |
| 62569 | 186 | case some => some | 
| 187 | } | |
| 62602 | 188 | |
| 189 | cleanup() | |
| 62574 | 190 | } | 
| 62569 | 191 | |
| 62574 | 192 | |
| 193 | // join | |
| 194 | ||
| 74141 | 195 | def join(): Int = | 
| 62574 | 196 |     {
 | 
| 74141 | 197 | val rc = proc.waitFor() | 
| 62602 | 198 | 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 | 199 | 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 | 200 | } | 
| 62543 | 201 | |
| 202 | ||
| 62574 | 203 | // result | 
| 62543 | 204 | |
| 205 | def result( | |
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 206 | input: String = "", | 
| 62543 | 207 | progress_stdout: String => Unit = (_: String) => (), | 
| 208 | progress_stderr: String => Unit = (_: String) => (), | |
| 72598 | 209 | watchdog: Option[Watchdog] = None, | 
| 62543 | 210 | strict: Boolean = true): Process_Result = | 
| 211 |     {
 | |
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 212 | val in = | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 213 | if (input.isEmpty) Future.value(stdin.close()) | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 214 |         else Future.thread("bash_stdin") { stdin.write(input); stdin.flush(); stdin.close(); }
 | 
| 62543 | 215 | val out_lines = | 
| 72105 | 216 |         Future.thread("bash_stdout") { File.read_lines(stdout, progress_stdout) }
 | 
| 62543 | 217 | val err_lines = | 
| 72105 | 218 |         Future.thread("bash_stderr") { File.read_lines(stderr, progress_stderr) }
 | 
| 62543 | 219 | |
| 72598 | 220 | val watchdog_thread = | 
| 221 | for ((time, check) <- watchdog) | |
| 222 |         yield {
 | |
| 223 |           Future.thread("bash_watchdog") {
 | |
| 224 |             while (proc.isAlive) {
 | |
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73604diff
changeset | 225 | time.sleep() | 
| 72598 | 226 | if (check(this)) interrupt() | 
| 227 | } | |
| 228 | } | |
| 229 | } | |
| 230 | ||
| 62543 | 231 | val rc = | 
| 74141 | 232 |         try { join() }
 | 
| 74306 | 233 |         catch { case Exn.Interrupt() => terminate(); Process_Result.RC.interrupt }
 | 
| 72598 | 234 | |
| 73367 | 235 | watchdog_thread.foreach(_.cancel()) | 
| 72598 | 236 | |
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 237 | in.join | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 238 | out_lines.join | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 239 | err_lines.join | 
| 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 240 | |
| 74306 | 241 | if (strict && rc == Process_Result.RC.interrupt) throw Exn.Interrupt() | 
| 62543 | 242 | |
| 73134 
8a8ffe78eee7
clarified return code: re-use SIGALRM for soft timeout;
 wenzelm parents: 
72598diff
changeset | 243 | Process_Result(rc, out_lines.join, err_lines.join, get_timing) | 
| 62543 | 244 | } | 
| 60991 | 245 | } | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 246 | |
| 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 247 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 248 | /* server */ | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 249 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 250 | object 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 | // input messages | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 253 | private val RUN = "run" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 254 | private val KILL = "kill" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 255 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 256 | // output messages | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 257 | private val UUID = "uuid" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 258 | private val INTERRUPT = "interrupt" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 259 | private val FAILURE = "failure" | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 260 | 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 | 261 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 262 | def start(port: Int = 0, debugging: => Boolean = false): Server = | 
| 
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 | val server = new Server(port, debugging) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 265 | server.start() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 266 | server | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 267 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 268 | } | 
| 
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 | class Server private(port: Int, debugging: => Boolean) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 271 | extends isabelle.Server.Handler(port) | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 272 |   {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 273 | server => | 
| 
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 | 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 | 276 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 277 | override def stop(): Unit = | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 278 |     {
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 279 | for ((_, process) <- _processes.value) process.terminate() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 280 | super.stop() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 281 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 282 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 283 | override def handle(connection: isabelle.Server.Connection): Unit = | 
| 
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 | def reply(chunks: List[String]): Unit = | 
| 74273 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 286 |         try { connection.write_byte_message(chunks.map(Bytes.apply)) }
 | 
| 
0a4cdf0036a0
more robust: client could have terminated already;
 wenzelm parents: 
74212diff
changeset | 287 |         catch { case _: IOException => }
 | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 288 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 289 | def reply_failure(exn: Throwable): Unit = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 290 | reply( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 291 | if (Exn.is_interrupt(exn)) List(Server.INTERRUPT) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 292 | else List(Server.FAILURE, Exn.message(exn))) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 293 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 294 | def reply_result(result: Process_Result): Unit = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 295 | reply( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 296 | Server.RESULT :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 297 | result.rc.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 298 | result.timing.elapsed.ms.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 299 | result.timing.cpu.ms.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 300 | result.out_lines.length.toString :: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 301 | result.out_lines ::: | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 302 | result.err_lines) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 303 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 304 |       connection.read_byte_message().map(_.map(_.text)) match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 305 | case None => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 306 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 307 | case Some(List(Server.KILL, UUID(uuid))) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 308 |           if (debugging) Output.writeln("kill " + uuid)
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 309 | _processes.value.get(uuid).foreach(_.terminate()) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 310 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 311 | 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 | 312 | Value.Boolean(redirect), Value.Seconds(timeout), description)) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 313 | val uuid = UUID.random() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 314 | |
| 74212 | 315 | val descr = make_description(description) | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 316 |           if (debugging) {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 317 | Output.writeln( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 318 | "start " + quote(descr) + " (uuid=" + uuid + ", timeout=" + timeout.seconds + ")") | 
| 74142 
0f051404f487
clarified signature: more options for bash_process;
 wenzelm parents: 
74141diff
changeset | 319 | } | 
| 73228 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 320 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 321 |           Exn.capture {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 322 | Bash.process(script, | 
| 74212 | 323 | description = description, | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 324 | cwd = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 325 |                 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 | 326 | case None => null | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 327 | case Some(s) => Path.explode(s).file | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 328 | }, | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 329 | env = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 330 | Isabelle_System.settings( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 331 | 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 | 332 | YXML.parse_body(putenv))), | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 333 | redirect= redirect) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 334 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 335 |           match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 336 | case Exn.Exn(exn) => reply_failure(exn) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 337 | case Exn.Res(process) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 338 | _processes.change(processes => processes + (uuid -> process)) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 339 | 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 | 340 | |
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 341 |               Isabelle_Thread.fork(name = "bash_process") {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 342 | @volatile var is_timeout = false | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 343 | val watchdog: Option[Watchdog] = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 344 |                   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 | 345 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 346 |                 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 | 347 |                 match {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 348 | case Exn.Exn(exn) => reply_failure(exn) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 349 | case Exn.Res(res0) => | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 350 | 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 | 351 |                     if (debugging) {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 352 | Output.writeln( | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 353 | "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 | 354 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 355 | reply_result(res) | 
| 
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 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 358 | _processes.change(provers => provers - uuid) | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 359 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 360 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 361 | connection.await_close() | 
| 
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 |         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 | 365 | } | 
| 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 366 | } | 
| 
0575cfd2ecfc
support multi-threaded Bash.process invocation on Apple Silicon, where Poly/ML is running on Rosetta 2;
 wenzelm parents: 
73134diff
changeset | 367 | } | 
| 74147 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 368 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 369 | class Handler extends Session.Protocol_Handler | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 370 |   {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 371 | private var server: Server = null | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 372 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 373 | override def init(session: Session): Unit = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 374 |     {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 375 | exit() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 376 |       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 | 377 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 378 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 379 | override def exit(): Unit = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 380 |     {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 381 |       if (server != null) {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 382 | server.stop() | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 383 | server = null | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 384 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 385 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 386 | |
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 387 | override def prover_options(options: Options): Options = | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 388 |     {
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 389 | val address = if (server == null) "" else server.address | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 390 | val password = if (server == null) "" else server.password | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 391 | options + | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 392 |         ("bash_process_address=" + address) +
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 393 |         ("bash_process_password=" + password)
 | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 394 | } | 
| 
d030b988d470
provide bash_process server for Isabelle/ML and other external programs;
 wenzelm parents: 
74142diff
changeset | 395 | } | 
| 60991 | 396 | } |