src/Pure/Concurrent/bash.scala
changeset 60991 2fc5a44346b5
child 61025 636b578bfadd
equal deleted inserted replaced
60990:07592e217180 60991:2fc5a44346b5
       
     1 /*  Title:      Pure/Concurrent/bash.scala
       
     2     Author:     Makarius
       
     3 
       
     4 GNU bash processes, with propagation of interrupts.
       
     5 */
       
     6 
       
     7 package isabelle
       
     8 
       
     9 
       
    10 import java.io.{File => JFile, BufferedReader, InputStreamReader,
       
    11   BufferedWriter, OutputStreamWriter}
       
    12 
       
    13 
       
    14 object Bash
       
    15 {
       
    16   /** result **/
       
    17 
       
    18   final case class Result(out_lines: List[String], err_lines: List[String], rc: Int)
       
    19   {
       
    20     def out: String = cat_lines(out_lines)
       
    21     def err: String = cat_lines(err_lines)
       
    22     def add_err(s: String): Result = copy(err_lines = err_lines ::: List(s))
       
    23     def set_rc(i: Int): Result = copy(rc = i)
       
    24 
       
    25     def check_error: Result =
       
    26       if (rc == Exn.Interrupt.return_code) throw Exn.Interrupt()
       
    27       else if (rc != 0) error(err)
       
    28       else this
       
    29   }
       
    30 
       
    31 
       
    32 
       
    33   /** process **/
       
    34 
       
    35   def process(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*): Process =
       
    36     new Process(cwd, env, redirect, args:_*)
       
    37 
       
    38   class Process private [Bash](
       
    39       cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*)
       
    40     extends Prover.System_Process
       
    41   {
       
    42     private val params =
       
    43       List(File.standard_path(Path.explode("~~/lib/scripts/process")), "group", "-", "no_script")
       
    44     private val proc = Isabelle_System.execute_env(cwd, env, redirect, (params ::: args.toList):_*)
       
    45 
       
    46 
       
    47     // channels
       
    48 
       
    49     val stdin: BufferedWriter =
       
    50       new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset))
       
    51 
       
    52     val stdout: BufferedReader =
       
    53       new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset))
       
    54 
       
    55     val stderr: BufferedReader =
       
    56       new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset))
       
    57 
       
    58 
       
    59     // signals
       
    60 
       
    61     private val pid = stdout.readLine
       
    62 
       
    63     private def kill_cmd(signal: String): Int =
       
    64       Isabelle_System.execute(true, "/usr/bin/env", "bash", "-c", "kill -" + signal + " -" + pid)
       
    65         .waitFor
       
    66 
       
    67     private def kill(signal: String): Boolean =
       
    68       Exn.Interrupt.postpone { kill_cmd(signal); kill_cmd("0") == 0 } getOrElse true
       
    69 
       
    70     private def multi_kill(signal: String): Boolean =
       
    71     {
       
    72       var running = true
       
    73       var count = 10
       
    74       while (running && count > 0) {
       
    75         if (kill(signal)) {
       
    76           Exn.Interrupt.postpone {
       
    77             Thread.sleep(100)
       
    78             count -= 1
       
    79           }
       
    80         }
       
    81         else running = false
       
    82       }
       
    83       running
       
    84     }
       
    85 
       
    86     def interrupt() { multi_kill("INT") }
       
    87     def terminate() { multi_kill("INT") && multi_kill("TERM") && kill("KILL"); proc.destroy }
       
    88 
       
    89 
       
    90     // JVM shutdown hook
       
    91 
       
    92     private val shutdown_hook = new Thread { override def run = terminate() }
       
    93 
       
    94     try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
       
    95     catch { case _: IllegalStateException => }
       
    96 
       
    97     private def cleanup() =
       
    98       try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
       
    99       catch { case _: IllegalStateException => }
       
   100 
       
   101 
       
   102     /* result */
       
   103 
       
   104     def join: Int = { val rc = proc.waitFor; cleanup(); rc }
       
   105   }
       
   106 }