src/Pure/System/bash.scala
changeset 75393 87ebf5a50283
parent 74306 a117c076aa22
child 76146 a64f3496d93a
equal deleted inserted replaced
75388:b3ca4a6ed74b 75393:87ebf5a50283
    13   File => JFile, IOException}
    13   File => JFile, IOException}
    14 import scala.annotation.tailrec
    14 import scala.annotation.tailrec
    15 import scala.jdk.OptionConverters._
    15 import scala.jdk.OptionConverters._
    16 
    16 
    17 
    17 
    18 object Bash
    18 object Bash {
    19 {
       
    20   /* concrete syntax */
    19   /* concrete syntax */
    21 
    20 
    22   private def bash_chr(c: Byte): String =
    21   private def bash_chr(c: Byte): String = {
    23   {
       
    24     val ch = c.toChar
    22     val ch = c.toChar
    25     ch match {
    23     ch match {
    26       case '\t' => "$'\\t'"
    24       case '\t' => "$'\\t'"
    27       case '\n' => "$'\\n'"
    25       case '\n' => "$'\\n'"
    28       case '\f' => "$'\\f'"
    26       case '\f' => "$'\\f'"
    59       redirect: Boolean = false,
    57       redirect: Boolean = false,
    60       cleanup: () => Unit = () => ()): Process =
    58       cleanup: () => Unit = () => ()): Process =
    61     new Process(script, description, cwd, env, redirect, cleanup)
    59     new Process(script, description, cwd, env, redirect, cleanup)
    62 
    60 
    63   class Process private[Bash](
    61   class Process private[Bash](
    64       script: String,
    62     script: String,
    65       description: String,
    63     description: String,
    66       cwd: JFile,
    64     cwd: JFile,
    67       env: JMap[String, String],
    65     env: JMap[String, String],
    68       redirect: Boolean,
    66     redirect: Boolean,
    69       cleanup: () => Unit)
    67     cleanup: () => Unit
    70   {
    68   ) {
    71     override def toString: String = make_description(description)
    69     override def toString: String = make_description(description)
    72 
    70 
    73     private val timing_file = Isabelle_System.tmp_file("bash_timing")
    71     private val timing_file = Isabelle_System.tmp_file("bash_timing")
    74     private val timing = Synchronized[Option[Timing]](None)
    72     private val timing = Synchronized[Option[Timing]](None)
    75     def get_timing: Timing = timing.value getOrElse Timing.zero
    73     def get_timing: Timing = timing.value getOrElse Timing.zero
   121         case None => process_alive(group_pid)
   119         case None => process_alive(group_pid)
   122         case Some(file) =>
   120         case Some(file) =>
   123           file.exists() && process_alive(Library.trim_line(File.read(file)))
   121           file.exists() && process_alive(Library.trim_line(File.read(file)))
   124       }
   122       }
   125 
   123 
   126     @tailrec private def signal(s: String, count: Int = 1): Boolean =
   124     @tailrec private def signal(s: String, count: Int = 1): Boolean = {
   127     {
   125       count <= 0 || {
   128       count <= 0 ||
       
   129       {
       
   130         isabelle.setup.Environment.kill_process(group_pid, s)
   126         isabelle.setup.Environment.kill_process(group_pid, s)
   131         val running =
   127         val running =
   132           root_process_alive() ||
   128           root_process_alive() ||
   133           isabelle.setup.Environment.kill_process(group_pid, "0")
   129           isabelle.setup.Environment.kill_process(group_pid, "0")
   134         if (running) {
   130         if (running) {
   137         }
   133         }
   138         else false
   134         else false
   139       }
   135       }
   140     }
   136     }
   141 
   137 
   142     def terminate(): Unit = Isabelle_Thread.try_uninterruptible
   138     def terminate(): Unit = Isabelle_Thread.try_uninterruptible {
   143     {
       
   144       signal("INT", count = 7) && signal("TERM", count = 3) && signal("KILL")
   139       signal("INT", count = 7) && signal("TERM", count = 3) && signal("KILL")
   145       proc.destroy()
   140       proc.destroy()
   146       do_cleanup()
   141       do_cleanup()
   147     }
   142     }
   148 
   143 
   149     def interrupt(): Unit = Isabelle_Thread.try_uninterruptible
   144     def interrupt(): Unit = Isabelle_Thread.try_uninterruptible {
   150     {
       
   151       isabelle.setup.Environment.kill_process(group_pid, "INT")
   145       isabelle.setup.Environment.kill_process(group_pid, "INT")
   152     }
   146     }
   153 
   147 
   154 
   148 
   155     // JVM shutdown hook
   149     // JVM shutdown hook
   160     catch { case _: IllegalStateException => }
   154     catch { case _: IllegalStateException => }
   161 
   155 
   162 
   156 
   163     // cleanup
   157     // cleanup
   164 
   158 
   165     private def do_cleanup(): Unit =
   159     private def do_cleanup(): Unit = {
   166     {
       
   167       try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
   160       try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
   168       catch { case _: IllegalStateException => }
   161       catch { case _: IllegalStateException => }
   169 
   162 
   170       script_file.delete()
   163       script_file.delete()
   171       winpid_file.foreach(_.delete())
   164       winpid_file.foreach(_.delete())
   190     }
   183     }
   191 
   184 
   192 
   185 
   193     // join
   186     // join
   194 
   187 
   195     def join(): Int =
   188     def join(): Int = {
   196     {
       
   197       val rc = proc.waitFor()
   189       val rc = proc.waitFor()
   198       do_cleanup()
   190       do_cleanup()
   199       rc
   191       rc
   200     }
   192     }
   201 
   193 
   205     def result(
   197     def result(
   206       input: String = "",
   198       input: String = "",
   207       progress_stdout: String => Unit = (_: String) => (),
   199       progress_stdout: String => Unit = (_: String) => (),
   208       progress_stderr: String => Unit = (_: String) => (),
   200       progress_stderr: String => Unit = (_: String) => (),
   209       watchdog: Option[Watchdog] = None,
   201       watchdog: Option[Watchdog] = None,
   210       strict: Boolean = true): Process_Result =
   202       strict: Boolean = true
   211     {
   203     ): Process_Result = {
   212       val in =
   204       val in =
   213         if (input.isEmpty) Future.value(stdin.close())
   205         if (input.isEmpty) Future.value(stdin.close())
   214         else Future.thread("bash_stdin") { stdin.write(input); stdin.flush(); stdin.close(); }
   206         else Future.thread("bash_stdin") { stdin.write(input); stdin.flush(); stdin.close(); }
   215       val out_lines =
   207       val out_lines =
   216         Future.thread("bash_stdout") { File.read_lines(stdout, progress_stdout) }
   208         Future.thread("bash_stdout") { File.read_lines(stdout, progress_stdout) }
   245   }
   237   }
   246 
   238 
   247 
   239 
   248   /* server */
   240   /* server */
   249 
   241 
   250   object Server
   242   object Server {
   251   {
       
   252     // input messages
   243     // input messages
   253     private val RUN = "run"
   244     private val RUN = "run"
   254     private val KILL = "kill"
   245     private val KILL = "kill"
   255 
   246 
   256     // output messages
   247     // output messages
   257     private val UUID = "uuid"
   248     private val UUID = "uuid"
   258     private val INTERRUPT = "interrupt"
   249     private val INTERRUPT = "interrupt"
   259     private val FAILURE = "failure"
   250     private val FAILURE = "failure"
   260     private val RESULT = "result"
   251     private val RESULT = "result"
   261 
   252 
   262     def start(port: Int = 0, debugging: => Boolean = false): Server =
   253     def start(port: Int = 0, debugging: => Boolean = false): Server = {
   263     {
       
   264       val server = new Server(port, debugging)
   254       val server = new Server(port, debugging)
   265       server.start()
   255       server.start()
   266       server
   256       server
   267     }
   257     }
   268   }
   258   }
   269 
   259 
   270   class Server private(port: Int, debugging: => Boolean)
   260   class Server private(port: Int, debugging: => Boolean)
   271     extends isabelle.Server.Handler(port)
   261   extends isabelle.Server.Handler(port) {
   272   {
       
   273     server =>
   262     server =>
   274 
   263 
   275     private val _processes = Synchronized(Map.empty[UUID.T, Bash.Process])
   264     private val _processes = Synchronized(Map.empty[UUID.T, Bash.Process])
   276 
   265 
   277     override def stop(): Unit =
   266     override def stop(): Unit = {
   278     {
       
   279       for ((_, process) <- _processes.value) process.terminate()
   267       for ((_, process) <- _processes.value) process.terminate()
   280       super.stop()
   268       super.stop()
   281     }
   269     }
   282 
   270 
   283     override def handle(connection: isabelle.Server.Connection): Unit =
   271     override def handle(connection: isabelle.Server.Connection): Unit = {
   284     {
       
   285       def reply(chunks: List[String]): Unit =
   272       def reply(chunks: List[String]): Unit =
   286         try { connection.write_byte_message(chunks.map(Bytes.apply)) }
   273         try { connection.write_byte_message(chunks.map(Bytes.apply)) }
   287         catch { case _: IOException => }
   274         catch { case _: IOException => }
   288 
   275 
   289       def reply_failure(exn: Throwable): Unit =
   276       def reply_failure(exn: Throwable): Unit =
   364         case Some(_) => reply_failure(ERROR("Bad protocol message"))
   351         case Some(_) => reply_failure(ERROR("Bad protocol message"))
   365       }
   352       }
   366     }
   353     }
   367   }
   354   }
   368 
   355 
   369   class Handler extends Session.Protocol_Handler
   356   class Handler extends Session.Protocol_Handler {
   370   {
       
   371     private var server: Server = null
   357     private var server: Server = null
   372 
   358 
   373     override def init(session: Session): Unit =
   359     override def init(session: Session): Unit = {
   374     {
       
   375       exit()
   360       exit()
   376       server = Server.start(debugging = session.session_options.bool("bash_process_debugging"))
   361       server = Server.start(debugging = session.session_options.bool("bash_process_debugging"))
   377     }
   362     }
   378 
   363 
   379     override def exit(): Unit =
   364     override def exit(): Unit = {
   380     {
       
   381       if (server != null) {
   365       if (server != null) {
   382         server.stop()
   366         server.stop()
   383         server = null
   367         server = null
   384       }
   368       }
   385     }
   369     }
   386 
   370 
   387     override def prover_options(options: Options): Options =
   371     override def prover_options(options: Options): Options = {
   388     {
       
   389       val address = if (server == null) "" else server.address
   372       val address = if (server == null) "" else server.address
   390       val password = if (server == null) "" else server.password
   373       val password = if (server == null) "" else server.password
   391       options +
   374       options +
   392         ("bash_process_address=" + address) +
   375         ("bash_process_address=" + address) +
   393         ("bash_process_password=" + password)
   376         ("bash_process_password=" + password)