| author | wenzelm | 
| Thu, 02 Nov 2023 10:29:24 +0100 | |
| changeset 78875 | b7d355b2b176 | 
| parent 77243 | 629dce95bb5c | 
| child 80357 | fe123d033e76 | 
| permissions | -rw-r--r-- | 
| 56385 | 1 | /* Title: Pure/PIDE/prover.scala | 
| 2 | Author: Makarius | |
| 57923 | 3 | Options: :folding=explicit: | 
| 56385 | 4 | |
| 57923 | 5 | Prover process wrapping. | 
| 56385 | 6 | */ | 
| 7 | ||
| 8 | package isabelle | |
| 9 | ||
| 10 | ||
| 67835 | 11 | import java.io.{InputStream, OutputStream, BufferedOutputStream, IOException}
 | 
| 57915 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 12 | |
| 
448325de6e4f
more abstract Prover.System_Process, which allows to bypass Isabelle_System.Managed_Process;
 wenzelm parents: 
57906diff
changeset | 13 | |
| 75393 | 14 | object Prover {
 | 
| 56385 | 15 | /* messages */ | 
| 16 | ||
| 17 | sealed abstract class Message | |
| 62556 
c115e69f457f
more abstract Session.start, without prover command-line;
 wenzelm parents: 
62310diff
changeset | 18 | type Receiver = Message => Unit | 
| 56385 | 19 | |
| 75393 | 20 |   class Input(val name: String, val args: List[String]) extends Message {
 | 
| 56385 | 21 | override def toString: String = | 
| 22 | XML.Elem(Markup(Markup.PROVER_COMMAND, List((Markup.NAME, name))), | |
| 71383 | 23 | args.flatMap(s => | 
| 24 | List(XML.newline, XML.elem(Markup.PROVER_ARG, YXML.parse_body(s))))).toString | |
| 56385 | 25 | } | 
| 26 | ||
| 75393 | 27 |   class Output(val message: XML.Elem) extends Message {
 | 
| 56385 | 28 | def kind: String = message.markup.name | 
| 29 | def properties: Properties.T = message.markup.properties | |
| 30 | def body: XML.Body = message.body | |
| 31 | ||
| 71601 | 32 | def is_init: Boolean = kind == Markup.INIT | 
| 33 | def is_exit: Boolean = kind == Markup.EXIT | |
| 34 | def is_stdout: Boolean = kind == Markup.STDOUT | |
| 35 | def is_stderr: Boolean = kind == Markup.STDERR | |
| 36 | def is_system: Boolean = kind == Markup.SYSTEM | |
| 37 | def is_status: Boolean = kind == Markup.STATUS | |
| 38 | def is_report: Boolean = kind == Markup.REPORT | |
| 39 | def is_syslog: Boolean = is_init || is_exit || is_system || is_stderr | |
| 56385 | 40 | |
| 75393 | 41 |     override def toString: String = {
 | 
| 56385 | 42 | val res = | 
| 43 | if (is_status || is_report) message.body.map(_.toString).mkString | |
| 71649 | 44 | else Pretty.string_of(message.body, metric = Symbol.Metric) | 
| 56385 | 45 | if (properties.isEmpty) | 
| 73559 | 46 | kind + " [[" + res + "]]" | 
| 56385 | 47 | else | 
| 73559 | 48 | kind + " " + | 
| 73712 | 49 |           (properties.map(Properties.Eq.apply)).mkString("{", ",", "}") + " [[" + res + "]]"
 | 
| 56385 | 50 | } | 
| 51 | } | |
| 52 | ||
| 73560 | 53 |   class Malformed(msg: String) extends Exn.User_Error("Malformed prover message: " + msg)
 | 
| 54 |   def bad_header(print: String): Nothing = throw new Malformed("bad message header\n" + print)
 | |
| 55 |   def bad_chunks(): Nothing = throw new Malformed("bad message chunks")
 | |
| 73559 | 56 | |
| 57 | def the_chunk(chunks: List[Bytes], print: => String): Bytes = | |
| 58 |     chunks match {
 | |
| 59 | case List(chunk) => chunk | |
| 73560 | 60 |       case _ => throw new Malformed("single chunk expected: " + print)
 | 
| 73559 | 61 | } | 
| 62 | ||
| 63 | class Protocol_Output(props: Properties.T, val chunks: List[Bytes]) | |
| 75393 | 64 |   extends Output(XML.Elem(Markup(Markup.PROTOCOL, props), Nil)) {
 | 
| 73559 | 65 | def chunk: Bytes = the_chunk(chunks, toString) | 
| 66 | lazy val text: String = chunk.text | |
| 56385 | 67 | } | 
| 68 | } | |
| 69 | ||
| 56393 
22f533e6a049
more abstract Prover.Syntax, as proposed by Carst Tankink;
 wenzelm parents: 
56387diff
changeset | 70 | |
| 65345 
2fdd4431b30e
clarified YXML vs. symbol encoding: operate on whole message;
 wenzelm parents: 
65317diff
changeset | 71 | class Prover( | 
| 62556 
c115e69f457f
more abstract Session.start, without prover command-line;
 wenzelm parents: 
62310diff
changeset | 72 | receiver: Prover.Receiver, | 
| 73031 
f93f0597f4fb
clarified signature: absorb XZ.Cache into XML.Cache;
 wenzelm parents: 
71969diff
changeset | 73 | cache: XML.Cache, | 
| 65316 | 74 | channel: System_Channel, | 
| 75393 | 75 | process: Bash.Process | 
| 76 | ) extends Protocol {
 | |
| 57923 | 77 | /** receiver output **/ | 
| 57916 | 78 | |
| 75393 | 79 |   private def system_output(text: String): Unit = {
 | 
| 57916 | 80 | receiver(new Prover.Output(XML.Elem(Markup(Markup.SYSTEM, Nil), List(XML.Text(text))))) | 
| 81 | } | |
| 82 | ||
| 75393 | 83 |   private def protocol_output(props: Properties.T, chunks: List[Bytes]): Unit = {
 | 
| 73562 | 84 | receiver(new Prover.Protocol_Output(props, chunks)) | 
| 57916 | 85 | } | 
| 86 | ||
| 75393 | 87 |   private def output(kind: String, props: Properties.T, body: XML.Body): Unit = {
 | 
| 59713 | 88 | val main = XML.Elem(Markup(kind, props), Protocol_Message.clean_reports(body)) | 
| 89 | val reports = Protocol_Message.reports(props, body) | |
| 73031 
f93f0597f4fb
clarified signature: absorb XZ.Cache into XML.Cache;
 wenzelm parents: 
71969diff
changeset | 90 | for (msg <- main :: reports) receiver(new Prover.Output(cache.elem(msg))) | 
| 57916 | 91 | } | 
| 92 | ||
| 75393 | 93 |   private def exit_message(result: Process_Result): Unit = {
 | 
| 65317 | 94 | output(Markup.EXIT, Markup.Process_Result(result), | 
| 71747 | 95 | List(XML.Text(result.print_return_code))) | 
| 57916 | 96 | } | 
| 97 | ||
| 98 | ||
| 56387 | 99 | |
| 57916 | 100 | /** process manager **/ | 
| 101 | ||
| 65317 | 102 | private val process_result: Future[Process_Result] = | 
| 103 |     Future.thread("process_result") {
 | |
| 74141 | 104 | val rc = process.join() | 
| 65317 | 105 | val timing = process.get_timing | 
| 106 | Process_Result(rc, timing = timing) | |
| 107 | } | |
| 57916 | 108 | |
| 75393 | 109 |   private def terminate_process(): Unit = {
 | 
| 73367 | 110 |     try { process.terminate() }
 | 
| 57916 | 111 |     catch {
 | 
| 112 |       case exn @ ERROR(_) => system_output("Failed to terminate prover process: " + exn.getMessage)
 | |
| 113 | } | |
| 56387 | 114 | } | 
| 115 | ||
| 75393 | 116 |   private val process_manager = Isabelle_Thread.fork(name = "process_manager") {
 | 
| 71641 
c1409b9c2b22
proper startup for Pure: its use_prelude produces stdout before stderr protocol init;
 wenzelm parents: 
71601diff
changeset | 117 | val stdout = physical_output(false) | 
| 
c1409b9c2b22
proper startup for Pure: its use_prelude produces stdout before stderr protocol init;
 wenzelm parents: 
71601diff
changeset | 118 | |
| 75393 | 119 |     val (startup_failed, startup_errors) = {
 | 
| 57916 | 120 | var finished: Option[Boolean] = None | 
| 121 | val result = new StringBuilder(100) | |
| 65316 | 122 |       while (finished.isEmpty && (process.stderr.ready || !process_result.is_finished)) {
 | 
| 123 |         while (finished.isEmpty && process.stderr.ready) {
 | |
| 57916 | 124 |           try {
 | 
| 65316 | 125 | val c = process.stderr.read | 
| 57916 | 126 | if (c == 2) finished = Some(true) | 
| 127 | else result += c.toChar | |
| 128 | } | |
| 129 |           catch { case _: IOException => finished = Some(false) }
 | |
| 130 | } | |
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73562diff
changeset | 131 | Time.seconds(0.05).sleep() | 
| 57916 | 132 | } | 
| 133 | (finished.isEmpty || !finished.get, result.toString.trim) | |
| 134 | } | |
| 135 | if (startup_errors != "") system_output(startup_errors) | |
| 136 | ||
| 137 |     if (startup_failed) {
 | |
| 138 | terminate_process() | |
| 139 | process_result.join | |
| 71641 
c1409b9c2b22
proper startup for Pure: its use_prelude produces stdout before stderr protocol init;
 wenzelm parents: 
71601diff
changeset | 140 | stdout.join | 
| 77243 | 141 | exit_message(Process_Result.startup_failure) | 
| 57916 | 142 | } | 
| 143 |     else {
 | |
| 65316 | 144 | val (command_stream, message_stream) = channel.rendezvous() | 
| 57916 | 145 | |
| 146 | command_input_init(command_stream) | |
| 147 | val stderr = physical_output(true) | |
| 148 | val message = message_output(message_stream) | |
| 149 | ||
| 65317 | 150 | val result = process_result.join | 
| 57916 | 151 |       system_output("process terminated")
 | 
| 152 | command_input_close() | |
| 74140 | 153 | for (thread <- List(stdout, stderr, message)) thread.join() | 
| 57916 | 154 |       system_output("process_manager terminated")
 | 
| 65317 | 155 | exit_message(result) | 
| 57916 | 156 | } | 
| 69572 
09a6a7c04b45
more robust system channel via options that are private to the user;
 wenzelm parents: 
68805diff
changeset | 157 | channel.shutdown() | 
| 57916 | 158 | } | 
| 159 | ||
| 160 | ||
| 161 | /* management methods */ | |
| 162 | ||
| 73340 | 163 | def join(): Unit = process_manager.join() | 
| 57916 | 164 | |
| 75393 | 165 |   def terminate(): Unit = {
 | 
| 62310 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 166 |     system_output("Terminating prover process")
 | 
| 57916 | 167 | command_input_close() | 
| 62310 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 168 | |
| 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 169 | var count = 10 | 
| 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 170 |     while (!process_result.is_finished && count > 0) {
 | 
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73562diff
changeset | 171 | Time.seconds(0.1).sleep() | 
| 62310 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 172 | count -= 1 | 
| 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 173 | } | 
| 
ab836dc7410e
more gentle termination (like Bash.multi_kill without signal) to give prover a chance to conclude;
 wenzelm parents: 
62307diff
changeset | 174 | if (!process_result.is_finished) terminate_process() | 
| 57916 | 175 | } | 
| 176 | ||
| 177 | ||
| 178 | ||
| 179 | /** process streams **/ | |
| 180 | ||
| 181 | /* command input */ | |
| 182 | ||
| 183 | private var command_input: Option[Consumer_Thread[List[Bytes]]] = None | |
| 184 | ||
| 73367 | 185 | private def command_input_close(): Unit = command_input.foreach(_.shutdown()) | 
| 57916 | 186 | |
| 75393 | 187 |   private def command_input_init(raw_stream: OutputStream): Unit = {
 | 
| 57916 | 188 | val name = "command_input" | 
| 189 | val stream = new BufferedOutputStream(raw_stream) | |
| 190 | command_input = | |
| 191 | Some( | |
| 192 | Consumer_Thread.fork(name)( | |
| 193 | consume = | |
| 194 |             {
 | |
| 195 | case chunks => | |
| 196 |                 try {
 | |
| 64004 | 197 |                   Bytes(chunks.map(_.length).mkString("", ",", "\n")).write_stream(stream)
 | 
| 198 | chunks.foreach(_.write_stream(stream)) | |
| 57916 | 199 | stream.flush | 
| 200 | true | |
| 201 | } | |
| 202 |                 catch { case e: IOException => system_output(name + ": " + e.getMessage); false }
 | |
| 203 | }, | |
| 73367 | 204 |           finish = { case () => stream.close(); system_output(name + " terminated") }
 | 
| 57916 | 205 | ) | 
| 206 | ) | |
| 207 | } | |
| 56387 | 208 | |
| 209 | ||
| 57916 | 210 | /* physical output */ | 
| 211 | ||
| 75393 | 212 |   private def physical_output(err: Boolean): Thread = {
 | 
| 57916 | 213 | val (name, reader, markup) = | 
| 65316 | 214 |       if (err) ("standard_error", process.stderr, Markup.STDERR)
 | 
| 215 |       else ("standard_output", process.stdout, Markup.STDOUT)
 | |
| 56387 | 216 | |
| 71692 | 217 |     Isabelle_Thread.fork(name = name) {
 | 
| 57916 | 218 |       try {
 | 
| 219 | var result = new StringBuilder(100) | |
| 220 | var finished = false | |
| 221 |         while (!finished) {
 | |
| 222 |           //{{{
 | |
| 223 | var c = -1 | |
| 224 | var done = false | |
| 71383 | 225 |           while (!done && (result.isEmpty || reader.ready)) {
 | 
| 57916 | 226 | c = reader.read | 
| 227 | if (c >= 0) result.append(c.asInstanceOf[Char]) | |
| 228 | else done = true | |
| 229 | } | |
| 71383 | 230 |           if (result.nonEmpty) {
 | 
| 65345 
2fdd4431b30e
clarified YXML vs. symbol encoding: operate on whole message;
 wenzelm parents: 
65317diff
changeset | 231 | output(markup, Nil, List(XML.Text(Symbol.decode(result.toString)))) | 
| 73367 | 232 | result.clear() | 
| 57916 | 233 | } | 
| 234 |           else {
 | |
| 73367 | 235 | reader.close() | 
| 57916 | 236 | finished = true | 
| 237 | } | |
| 238 | //}}} | |
| 239 | } | |
| 240 | } | |
| 241 |       catch { case e: IOException => system_output(name + ": " + e.getMessage) }
 | |
| 242 | system_output(name + " terminated") | |
| 243 | } | |
| 244 | } | |
| 56387 | 245 | |
| 246 | ||
| 57916 | 247 | /* message output */ | 
| 248 | ||
| 75393 | 249 |   private def message_output(stream: InputStream): Thread = {
 | 
| 73561 
c83152933579
clarified signature: Bytes extends CharSequence already (see d201996f72a8);
 wenzelm parents: 
73560diff
changeset | 250 | def decode_chunk(chunk: Bytes): XML.Body = | 
| 73562 | 251 | Symbol.decode_yxml_failsafe(chunk.text, cache = cache) | 
| 56387 | 252 | |
| 73559 | 253 | val thread_name = "message_output" | 
| 254 |     Isabelle_Thread.fork(name = thread_name) {
 | |
| 255 |       try {
 | |
| 256 | var finished = false | |
| 257 |         while (!finished) {
 | |
| 258 |           Byte_Message.read_message(stream) match {
 | |
| 259 | case None => finished = true | |
| 260 | case Some(header :: chunks) => | |
| 261 |               decode_chunk(header) match {
 | |
| 73562 | 262 | case List(XML.Elem(Markup(kind, props), Nil)) => | 
| 73559 | 263 | if (kind == Markup.PROTOCOL) protocol_output(props, chunks) | 
| 73562 | 264 | else output(kind, props, decode_chunk(Prover.the_chunk(chunks, kind))) | 
| 73559 | 265 | case _ => Prover.bad_header(header.toString) | 
| 266 | } | |
| 267 | case Some(_) => Prover.bad_chunks() | |
| 268 | } | |
| 57916 | 269 | } | 
| 270 | } | |
| 271 |       catch {
 | |
| 272 |         case e: IOException => system_output("Cannot read message:\n" + e.getMessage)
 | |
| 73560 | 273 | case e: Prover.Malformed => system_output(e.getMessage) | 
| 57916 | 274 | } | 
| 73367 | 275 | stream.close() | 
| 56387 | 276 | |
| 73559 | 277 | system_output(thread_name + " terminated") | 
| 57916 | 278 | } | 
| 279 | } | |
| 280 | ||
| 281 | ||
| 282 | ||
| 283 | /** protocol commands **/ | |
| 284 | ||
| 70666 | 285 | var trace: Boolean = false | 
| 286 | ||
| 70661 | 287 | def protocol_command_raw(name: String, args: List[Bytes]): Unit = | 
| 57916 | 288 |     command_input match {
 | 
| 74253 | 289 | case Some(thread) if thread.is_active() => | 
| 70666 | 290 |         if (trace) {
 | 
| 73359 | 291 |           val payload = args.foldLeft(0) { case (n, b) => n + b.length }
 | 
| 70666 | 292 | Output.writeln( | 
| 293 | "protocol_command " + name + ", args = " + args.length + ", payload = " + payload) | |
| 294 | } | |
| 295 | thread.send(Bytes(name) :: args) | |
| 68805 | 296 |       case _ => error("Inactive prover input thread for command " + quote(name))
 | 
| 57916 | 297 | } | 
| 298 | ||
| 75393 | 299 |   def protocol_command_args(name: String, args: List[String]): Unit = {
 | 
| 70661 | 300 | receiver(new Prover.Input(name, args)) | 
| 301 | protocol_command_raw(name, args.map(Bytes(_))) | |
| 57916 | 302 | } | 
| 70661 | 303 | |
| 304 | def protocol_command(name: String, args: String*): Unit = | |
| 305 | protocol_command_args(name, args.toList) | |
| 56387 | 306 | } |