| author | wenzelm | 
| Sun, 16 May 2021 23:22:03 +0200 | |
| changeset 73710 | 241cfa881788 | 
| parent 73702 | 7202e12cb324 | 
| child 74094 | 6113f1db4342 | 
| permissions | -rw-r--r-- | 
| 66347 | 1 | /* Title: Pure/Tools/server.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 4 | Resident Isabelle servers. | |
| 67809 | 5 | |
| 6 | Message formats: | |
| 7 | - short message (single line): | |
| 8 | NAME ARGUMENT | |
| 9 | - long message (multiple lines): | |
| 10 | BYTE_LENGTH | |
| 11 | NAME ARGUMENT | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 12 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 13 | Argument formats: | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 14 | - Unit as empty string | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 15 | - XML.Elem in YXML notation | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 16 | - JSON.T in standard notation | 
| 66347 | 17 | */ | 
| 18 | ||
| 19 | package isabelle | |
| 20 | ||
| 21 | ||
| 67837 | 22 | import java.io.{BufferedInputStream, BufferedOutputStream, InputStreamReader, OutputStreamWriter,
 | 
| 23 | IOException} | |
| 67797 | 24 | import java.net.{Socket, SocketException, SocketTimeoutException, ServerSocket, InetAddress}
 | 
| 66347 | 25 | |
| 26 | ||
| 27 | object Server | |
| 28 | {
 | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 29 | /* message argument */ | 
| 66927 | 30 | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 31 | object Argument | 
| 67794 | 32 |   {
 | 
| 67903 | 33 | def is_name_char(c: Char): Boolean = | 
| 34 | Symbol.is_ascii_letter(c) || Symbol.is_ascii_digit(c) || c == '_' || c == '.' | |
| 35 | ||
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 36 | def split(msg: String): (String, String) = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 37 |     {
 | 
| 71601 | 38 | val name = msg.takeWhile(is_name_char) | 
| 39 | val argument = msg.substring(name.length).dropWhile(Symbol.is_ascii_blank) | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 40 | (name, argument) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 41 | } | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 42 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 43 | def print(arg: Any): String = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 44 |       arg match {
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 45 | case () => "" | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 46 | case t: XML.Elem => YXML.string_of_tree(t) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 47 | case t: JSON.T => JSON.Format(t) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 48 | } | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 49 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 50 | def parse(argument: String): Any = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 51 | if (argument == "") () | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 52 | else if (YXML.detect_elem(argument)) YXML.parse_elem(argument) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 53 | else JSON.parse(argument, strict = false) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 54 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 55 | def unapply(argument: String): Option[Any] = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 56 |       try { Some(parse(argument)) }
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 57 |       catch { case ERROR(_) => None }
 | 
| 67794 | 58 | } | 
| 59 | ||
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 60 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 61 | /* input command */ | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 62 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 63 | type Command_Body = PartialFunction[(Context, Any), Any] | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 64 | |
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 65 | abstract class Command(val command_name: String) | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 66 |   {
 | 
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 67 | def command_body: Command_Body | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 68 | override def toString: String = command_name | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 69 | } | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 70 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 71 | class Commands(commands: Command*) extends Isabelle_System.Service | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 72 |   {
 | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 73 | def entries: List[Command] = commands.toList | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 74 | } | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 75 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 76 | private lazy val command_table: Map[String, Command] = | 
| 73359 | 77 | Isabelle_System.make_services(classOf[Commands]).flatMap(_.entries). | 
| 78 |       foldLeft(Map.empty[String, Command]) {
 | |
| 79 | case (cmds, cmd) => | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 80 | val name = cmd.command_name | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 81 |           if (cmds.isDefinedAt(name)) error("Duplicate Isabelle server command: " + quote(name))
 | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 82 | else cmds + (name -> cmd) | 
| 73359 | 83 | } | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 84 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 85 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 86 | /* output reply */ | 
| 66929 | 87 | |
| 67857 | 88 | class Error(val message: String, val json: JSON.Object.T = JSON.Object.empty) | 
| 89 | extends RuntimeException(message) | |
| 90 | ||
| 67891 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 91 | def json_error(exn: Throwable): JSON.Object.T = | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 92 |     exn match {
 | 
| 67913 
d58fa3ed236f
proper order of matches: Server.Error is an instance of Exn.ERROR;
 wenzelm parents: 
67911diff
changeset | 93 | case e: Error => Reply.error_message(e.message) ++ e.json | 
| 67901 | 94 | case ERROR(msg) => Reply.error_message(msg) | 
| 95 | case _ if Exn.is_interrupt(exn) => Reply.error_message(Exn.message(exn)) | |
| 67891 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 96 | case _ => JSON.Object.empty | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 97 | } | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 98 | |
| 66927 | 99 | object Reply extends Enumeration | 
| 100 |   {
 | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 101 | val OK, ERROR, FINISHED, FAILED, NOTE = Value | 
| 67800 | 102 | |
| 67901 | 103 | def message(msg: String, kind: String = ""): JSON.Object.T = | 
| 67931 
f7917c15b566
field "kind" is always present, with default "writeln";
 wenzelm parents: 
67920diff
changeset | 104 | JSON.Object(Markup.KIND -> proper_string(kind).getOrElse(Markup.WRITELN), "message" -> msg) | 
| 67901 | 105 | |
| 106 | def error_message(msg: String): JSON.Object.T = | |
| 67911 
3cda747493d8
clarified markup according to common Command.Results;
 wenzelm parents: 
67904diff
changeset | 107 | message(msg, kind = Markup.ERROR) | 
| 67857 | 108 | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 109 | def unapply(msg: String): Option[(Reply.Value, Any)] = | 
| 67800 | 110 |     {
 | 
| 67809 | 111 | if (msg == "") None | 
| 67800 | 112 |       else {
 | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 113 | val (name, argument) = Argument.split(msg) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 114 |         for {
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 115 | reply <- | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 116 |             try { Some(withName(name)) }
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 117 |             catch { case _: NoSuchElementException => None }
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 118 | arg <- Argument.unapply(argument) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 119 | } yield (reply, arg) | 
| 67800 | 120 | } | 
| 121 | } | |
| 66927 | 122 | } | 
| 123 | ||
| 124 | ||
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 125 | /* handler: port, password, thread */ | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 126 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 127 | abstract class Handler(port0: Int) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 128 |   {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 129 | val socket: ServerSocket = new ServerSocket(port0, 50, Server.localhost) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 130 | def port: Int = socket.getLocalPort | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 131 | val password: String = UUID.random_string() | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 132 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 133 | override def toString: String = print(port, password) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 134 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 135 | def handle(connection: Server.Connection): Unit | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 136 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 137 | private lazy val thread: Thread = | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 138 |       Isabelle_Thread.fork(name = "server_handler") {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 139 | var finished = false | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 140 |         while (!finished) {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 141 |           Exn.capture(socket.accept) match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 142 | case Exn.Res(client) => | 
| 73179 
f9c71ce29150
tuned name, e.g. relevant for Naproche-SAD debugging in Isabelle/jEdit;
 wenzelm parents: 
73135diff
changeset | 143 |               Isabelle_Thread.fork(name = "client") {
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 144 | using(Connection(client))(connection => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 145 | if (connection.read_password(password)) handle(connection)) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 146 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 147 | case Exn.Exn(_) => finished = true | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 148 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 149 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 150 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 151 | |
| 73367 | 152 | def start(): Unit = thread | 
| 73340 | 153 | def join: Unit = thread.join | 
| 73367 | 154 |     def stop(): Unit = { socket.close(); join }
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 155 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 156 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 157 | |
| 67786 | 158 | /* socket connection */ | 
| 159 | ||
| 160 | object Connection | |
| 161 |   {
 | |
| 162 | def apply(socket: Socket): Connection = | |
| 163 | new Connection(socket) | |
| 164 | } | |
| 165 | ||
| 69393 
ed0824ef337e
static type for Library.using: avoid Java 11 warnings on "Illegal reflective access";
 wenzelm parents: 
69033diff
changeset | 166 | class Connection private(socket: Socket) extends AutoCloseable | 
| 67786 | 167 |   {
 | 
| 168 | override def toString: String = socket.toString | |
| 169 | ||
| 73367 | 170 | def close(): Unit = socket.close() | 
| 67786 | 171 | |
| 73340 | 172 | def set_timeout(t: Time): Unit = socket.setSoTimeout(t.ms.toInt) | 
| 67832 | 173 | |
| 174 | private val in = new BufferedInputStream(socket.getInputStream) | |
| 175 | private val out = new BufferedOutputStream(socket.getOutputStream) | |
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 176 | private val out_lock: AnyRef = new Object | 
| 67786 | 177 | |
| 71713 | 178 | def tty_loop(): TTY_Loop = | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 179 | new TTY_Loop( | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 180 | new OutputStreamWriter(out), | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 181 | new InputStreamReader(in), | 
| 71713 | 182 | writer_lock = out_lock) | 
| 67837 | 183 | |
| 69464 | 184 | def read_password(password: String): Boolean = | 
| 185 |       try { Byte_Message.read_line(in).map(_.text) == Some(password) }
 | |
| 186 |       catch { case _: IOException => false }
 | |
| 187 | ||
| 67809 | 188 | def read_message(): Option[String] = | 
| 69451 | 189 |       try { Byte_Message.read_line_message(in).map(_.text) }
 | 
| 190 |       catch { case _: IOException => None }
 | |
| 67786 | 191 | |
| 73133 | 192 | def await_close(): Unit = | 
| 73135 | 193 |       try { Byte_Message.read(in, 1); socket.close() }
 | 
| 73133 | 194 |       catch { case _: IOException => }
 | 
| 195 | ||
| 69448 | 196 | def write_message(msg: String): Unit = | 
| 197 |       out_lock.synchronized { Byte_Message.write_line_message(out, Bytes(UTF8.bytes(msg))) }
 | |
| 67786 | 198 | |
| 73340 | 199 | def reply(r: Reply.Value, arg: Any): Unit = | 
| 67786 | 200 |     {
 | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 201 | val argument = Argument.print(arg) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 202 | write_message(if (argument == "") r.toString else r.toString + " " + argument) | 
| 67786 | 203 | } | 
| 204 | ||
| 73340 | 205 | def reply_ok(arg: Any): Unit = reply(Reply.OK, arg) | 
| 206 | def reply_error(arg: Any): Unit = reply(Reply.ERROR, arg) | |
| 67857 | 207 | def reply_error_message(message: String, more: JSON.Object.Entry*): Unit = | 
| 67901 | 208 | reply_error(Reply.error_message(message) ++ more) | 
| 67801 | 209 | |
| 73340 | 210 | def notify(arg: Any): Unit = reply(Reply.NOTE, arg) | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 211 | } | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 212 | |
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 213 | |
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 214 | /* context with output channels */ | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 215 | |
| 67878 | 216 | class Context private[Server](val server: Server, connection: Connection) | 
| 69393 
ed0824ef337e
static type for Library.using: avoid Java 11 warnings on "Illegal reflective access";
 wenzelm parents: 
69033diff
changeset | 217 | extends AutoCloseable | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 218 |   {
 | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 219 | context => | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 220 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 221 | def command_list: List[String] = command_table.keys.toList.sorted | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 222 | |
| 73340 | 223 | def reply(r: Reply.Value, arg: Any): Unit = connection.reply(r, arg) | 
| 224 | def notify(arg: Any): Unit = connection.notify(arg) | |
| 67857 | 225 | def message(kind: String, msg: String, more: JSON.Object.Entry*): Unit = | 
| 67901 | 226 | notify(Reply.message(msg, kind = kind) ++ more) | 
| 67857 | 227 | def writeln(msg: String, more: JSON.Object.Entry*): Unit = message(Markup.WRITELN, msg, more:_*) | 
| 228 | def warning(msg: String, more: JSON.Object.Entry*): Unit = message(Markup.WARNING, msg, more:_*) | |
| 229 | def error_message(msg: String, more: JSON.Object.Entry*): Unit = | |
| 67911 
3cda747493d8
clarified markup according to common Command.Results;
 wenzelm parents: 
67904diff
changeset | 230 | message(Markup.ERROR, msg, more:_*) | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 231 | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 232 | def progress(more: JSON.Object.Entry*): Connection_Progress = | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 233 | new Connection_Progress(context, more:_*) | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 234 | |
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 235 | override def toString: String = connection.toString | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 236 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 237 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 238 | /* asynchronous tasks */ | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 239 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 240 | private val _tasks = Synchronized(Set.empty[Task]) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 241 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 242 | def make_task(body: Task => JSON.Object.T): Task = | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 243 |     {
 | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 244 | val task = new Task(context, body) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 245 | _tasks.change(_ + task) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 246 | task | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 247 | } | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 248 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 249 | def remove_task(task: Task): Unit = | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 250 | _tasks.change(_ - task) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 251 | |
| 69458 | 252 | def cancel_task(id: UUID.T): Unit = | 
| 73367 | 253 |       _tasks.change(tasks => { tasks.find(task => task.id == id).foreach(_.cancel()); tasks })
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 254 | |
| 73340 | 255 | def close(): Unit = | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 256 |     {
 | 
| 73367 | 257 |       while(_tasks.change_result(tasks => { tasks.foreach(_.cancel()); (tasks.nonEmpty, tasks) }))
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 258 |       { _tasks.value.foreach(_.join) }
 | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 259 | } | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 260 | } | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 261 | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 262 | class Connection_Progress private[Server](context: Context, more: JSON.Object.Entry*) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 263 | extends Progress | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 264 |   {
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 265 | override def echo(msg: String): Unit = context.writeln(msg, more:_*) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 266 | override def echo_warning(msg: String): Unit = context.warning(msg, more:_*) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 267 | override def echo_error_message(msg: String): Unit = context.error_message(msg, more:_*) | 
| 68770 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 268 | |
| 73340 | 269 | override def theory(theory: Progress.Theory): Unit = | 
| 68957 | 270 |     {
 | 
| 271 | val entries: List[JSON.Object.Entry] = | |
| 272 |         List("theory" -> theory.theory, "session" -> theory.session) :::
 | |
| 273 |           (theory.percentage match { case None => Nil case Some(p) => List("percentage" -> p) })
 | |
| 274 | context.writeln(theory.message, entries ::: more.toList:_*) | |
| 275 | } | |
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 276 | |
| 73340 | 277 | override def nodes_status(nodes_status: Document_Status.Nodes_Status): Unit = | 
| 68770 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 278 |     {
 | 
| 68903 | 279 | val json = | 
| 69818 
60d0ee8f2ddb
more robust: avoid potentially unrelated snapshot for the sake of is_suppressed;
 wenzelm parents: 
69817diff
changeset | 280 | for ((name, node_status) <- nodes_status.present) | 
| 73132 | 281 |           yield name.json + ("status" -> node_status.json)
 | 
| 68770 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 282 | context.notify(JSON.Object(Markup.KIND -> Markup.NODES_STATUS, Markup.NODES_STATUS -> json)) | 
| 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 283 | } | 
| 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 284 | |
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 285 | override def toString: String = context.toString | 
| 67786 | 286 | } | 
| 287 | ||
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 288 | class Task private[Server](val context: Context, body: Task => JSON.Object.T) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 289 |   {
 | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 290 | task => | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 291 | |
| 69458 | 292 | val id: UUID.T = UUID.random() | 
| 67885 | 293 |     val ident: JSON.Object.Entry = ("task" -> id.toString)
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 294 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 295 | val progress: Connection_Progress = context.progress(ident) | 
| 73367 | 296 | def cancel(): Unit = progress.stop() | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 297 | |
| 71692 | 298 | private lazy val thread = Isabelle_Thread.fork(name = "server_task") | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 299 |     {
 | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 300 |       Exn.capture { body(task) } match {
 | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 301 | case Exn.Res(res) => | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 302 | context.reply(Reply.FINISHED, res + ident) | 
| 67891 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 303 | case Exn.Exn(exn) => | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 304 | val err = json_error(exn) | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 305 | if (err.isEmpty) throw exn else context.reply(Reply.FAILED, err + ident) | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 306 | } | 
| 73367 | 307 | progress.stop() | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 308 | context.remove_task(task) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 309 | } | 
| 73340 | 310 | def start: Unit = thread | 
| 311 | def join: Unit = thread.join | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 312 | } | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 313 | |
| 67786 | 314 | |
| 67807 | 315 | /* server info */ | 
| 316 | ||
| 69463 | 317 | val localhost_name: String = "127.0.0.1" | 
| 318 | def localhost: InetAddress = InetAddress.getByName(localhost_name) | |
| 319 | ||
| 320 | def print_address(port: Int): String = localhost_name + ":" + port | |
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 321 | |
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 322 | def print(port: Int, password: String): String = | 
| 69461 | 323 | print_address(port) + " (password " + quote(password) + ")" | 
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 324 | |
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 325 | object Info | 
| 67807 | 326 |   {
 | 
| 69463 | 327 | private val Pattern = | 
| 328 |       ("""server "([^"]*)" = \Q""" + localhost_name + """\E:(\d+) \(password "([^"]*)"\)""").r
 | |
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 329 | |
| 69463 | 330 | def parse(s: String): Option[Info] = | 
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 331 |       s match {
 | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 332 | case Pattern(name, Value.Int(port), password) => Some(Info(name, port, password)) | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 333 | case _ => None | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 334 | } | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 335 | |
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 336 | def apply(name: String, port: Int, password: String): Info = | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 337 | new Info(name, port, password) | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 338 | } | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 339 | |
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 340 | class Info private(val name: String, val port: Int, val password: String) | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 341 |   {
 | 
| 69461 | 342 | def address: String = print_address(port) | 
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 343 | |
| 67807 | 344 | override def toString: String = | 
| 67821 
82fb12061069
more uniform output: this may be parsed by another program;
 wenzelm parents: 
67820diff
changeset | 345 | "server " + quote(name) + " = " + print(port, password) | 
| 67807 | 346 | |
| 347 | def connection(): Connection = | |
| 348 |     {
 | |
| 69463 | 349 | val connection = Connection(new Socket(localhost, port)) | 
| 67809 | 350 | connection.write_message(password) | 
| 67807 | 351 | connection | 
| 352 | } | |
| 353 | ||
| 73367 | 354 | def active: Boolean = | 
| 67807 | 355 |       try {
 | 
| 356 | using(connection())(connection => | |
| 357 |           {
 | |
| 67832 | 358 | connection.set_timeout(Time.seconds(2.0)) | 
| 67867 | 359 |             connection.read_message() match {
 | 
| 360 | case Some(Reply(Reply.OK, _)) => true | |
| 361 | case _ => false | |
| 362 | } | |
| 67807 | 363 | }) | 
| 364 | } | |
| 365 |       catch {
 | |
| 366 | case _: IOException => false | |
| 367 | case _: SocketException => false | |
| 368 | case _: SocketTimeoutException => false | |
| 369 | } | |
| 370 | } | |
| 371 | ||
| 372 | ||
| 66347 | 373 | /* per-user servers */ | 
| 374 | ||
| 67822 | 375 | val default_name = "isabelle" | 
| 376 | ||
| 66347 | 377 | object Data | 
| 378 |   {
 | |
| 66349 | 379 |     val database = Path.explode("$ISABELLE_HOME_USER/servers.db")
 | 
| 66347 | 380 | |
| 66857 | 381 |     val name = SQL.Column.string("name").make_primary_key
 | 
| 66349 | 382 |     val port = SQL.Column.int("port")
 | 
| 66347 | 383 |     val password = SQL.Column.string("password")
 | 
| 66349 | 384 |     val table = SQL.Table("isabelle_servers", List(name, port, password))
 | 
| 66347 | 385 | } | 
| 386 | ||
| 67807 | 387 | def list(db: SQLite.Database): List[Info] = | 
| 66347 | 388 |     if (db.tables.contains(Data.table.name)) {
 | 
| 389 | db.using_statement(Data.table.select())(stmt => | |
| 390 | stmt.execute_query().iterator(res => | |
| 67807 | 391 | Info( | 
| 66349 | 392 | res.string(Data.name), | 
| 393 | res.int(Data.port), | |
| 394 | res.string(Data.password))).toList.sortBy(_.name)) | |
| 66347 | 395 | } | 
| 396 | else Nil | |
| 397 | ||
| 67807 | 398 | def find(db: SQLite.Database, name: String): Option[Info] = | 
| 399 | list(db).find(server_info => server_info.name == name && server_info.active) | |
| 66347 | 400 | |
| 67870 | 401 | def init( | 
| 402 | name: String = default_name, | |
| 403 | port: Int = 0, | |
| 404 | existing_server: Boolean = false, | |
| 405 | log: Logger = No_Logger): (Info, Option[Server]) = | |
| 66347 | 406 |   {
 | 
| 407 | using(SQLite.open_database(Data.database))(db => | |
| 67799 | 408 |       {
 | 
| 409 |         db.transaction {
 | |
| 71114 | 410 |           Isabelle_System.chmod("600", Data.database)
 | 
| 67799 | 411 | db.create_table(Data.table) | 
| 67807 | 412 | list(db).filterNot(_.active).foreach(server_info => | 
| 413 | db.using_statement(Data.table.delete(Data.name.where_equal(server_info.name)))( | |
| 73367 | 414 | _.execute())) | 
| 67799 | 415 | } | 
| 416 |         db.transaction {
 | |
| 417 |           find(db, name) match {
 | |
| 67807 | 418 | case Some(server_info) => (server_info, None) | 
| 67799 | 419 | case None => | 
| 67821 
82fb12061069
more uniform output: this may be parsed by another program;
 wenzelm parents: 
67820diff
changeset | 420 |               if (existing_server) error("Isabelle server " + quote(name) + " not running")
 | 
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 421 | |
| 67870 | 422 | val server = new Server(port, log) | 
| 67807 | 423 | val server_info = Info(name, server.port, server.password) | 
| 66347 | 424 | |
| 73367 | 425 | db.using_statement(Data.table.delete(Data.name.where_equal(name)))(_.execute()) | 
| 67799 | 426 | db.using_statement(Data.table.insert())(stmt => | 
| 427 |               {
 | |
| 67807 | 428 | stmt.string(1) = server_info.name | 
| 429 | stmt.int(2) = server_info.port | |
| 430 | stmt.string(3) = server_info.password | |
| 67799 | 431 | stmt.execute() | 
| 432 | }) | |
| 66348 | 433 | |
| 73367 | 434 | server.start() | 
| 67807 | 435 | (server_info, Some(server)) | 
| 67799 | 436 | } | 
| 66347 | 437 | } | 
| 438 | }) | |
| 439 | } | |
| 440 | ||
| 67822 | 441 | def exit(name: String = default_name): Boolean = | 
| 66347 | 442 |   {
 | 
| 443 | using(SQLite.open_database(Data.database))(db => | |
| 444 |       db.transaction {
 | |
| 445 |         find(db, name) match {
 | |
| 67807 | 446 | case Some(server_info) => | 
| 67809 | 447 |             using(server_info.connection())(_.write_message("shutdown"))
 | 
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73523diff
changeset | 448 |             while(server_info.active) { Time.seconds(0.05).sleep() }
 | 
| 66347 | 449 | true | 
| 67785 | 450 | case None => false | 
| 66347 | 451 | } | 
| 452 | }) | |
| 453 | } | |
| 454 | ||
| 455 | ||
| 456 | /* Isabelle tool wrapper */ | |
| 457 | ||
| 458 | val isabelle_tool = | |
| 72763 | 459 |     Isabelle_Tool("server", "manage resident Isabelle servers", Scala_Project.here, args =>
 | 
| 66347 | 460 |     {
 | 
| 67806 | 461 | var console = false | 
| 67870 | 462 | var log_file: Option[Path] = None | 
| 66348 | 463 | var operation_list = false | 
| 67823 | 464 | var operation_exit = false | 
| 67822 | 465 | var name = default_name | 
| 66348 | 466 | var port = 0 | 
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 467 | var existing_server = false | 
| 66347 | 468 | |
| 469 | val getopts = | |
| 470 |         Getopts("""
 | |
| 471 | Usage: isabelle server [OPTIONS] | |
| 472 | ||
| 473 | Options are: | |
| 67870 | 474 | -L FILE logging on FILE | 
| 67875 | 475 | -c console interaction with specified server | 
| 67904 | 476 | -l list servers (alternative operation) | 
| 67822 | 477 | -n NAME explicit server name (default: """ + default_name + """) | 
| 66348 | 478 | -p PORT explicit server port | 
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 479 | -s assume existing server, no implicit startup | 
| 67904 | 480 | -x exit specified server (alternative operation) | 
| 66347 | 481 | |
| 482 | Manage resident Isabelle servers. | |
| 483 | """, | |
| 67870 | 484 | "L:" -> (arg => log_file = Some(Path.explode(File.standard_path(arg)))), | 
| 67875 | 485 | "c" -> (_ => console = true), | 
| 67870 | 486 | "l" -> (_ => operation_list = true), | 
| 66348 | 487 | "n:" -> (arg => name = arg), | 
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 488 | "p:" -> (arg => port = Value.Int.parse(arg)), | 
| 67870 | 489 | "s" -> (_ => existing_server = true), | 
| 67876 | 490 | "x" -> (_ => operation_exit = true)) | 
| 66347 | 491 | |
| 492 | val more_args = getopts(args) | |
| 66348 | 493 | if (more_args.nonEmpty) getopts.usage() | 
| 66347 | 494 | |
| 66353 | 495 |       if (operation_list) {
 | 
| 67807 | 496 |         for {
 | 
| 71714 | 497 | server_info <- using(SQLite.open_database(Data.database))(list) | 
| 67807 | 498 | if server_info.active | 
| 499 | } Output.writeln(server_info.toString, stdout = true) | |
| 66353 | 500 | } | 
| 67823 | 501 |       else if (operation_exit) {
 | 
| 502 | val ok = Server.exit(name) | |
| 69033 | 503 | sys.exit(if (ok) 0 else 2) | 
| 67823 | 504 | } | 
| 66348 | 505 |       else {
 | 
| 67870 | 506 | val log = Logger.make(log_file) | 
| 507 | val (server_info, server) = | |
| 508 | init(name, port = port, existing_server = existing_server, log = log) | |
| 67807 | 509 | Output.writeln(server_info.toString, stdout = true) | 
| 67834 | 510 |         if (console) {
 | 
| 511 | using(server_info.connection())(connection => connection.tty_loop().join) | |
| 512 | } | |
| 67785 | 513 | server.foreach(_.join) | 
| 66348 | 514 | } | 
| 66347 | 515 | }) | 
| 516 | } | |
| 517 | ||
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 518 | class Server private(port0: Int, val log: Logger) extends Server.Handler(port0) | 
| 66347 | 519 | {
 | 
| 67785 | 520 | server => | 
| 521 | ||
| 69458 | 522 | private val _sessions = Synchronized(Map.empty[UUID.T, Headless.Session]) | 
| 523 |   def err_session(id: UUID.T): Nothing = error("No session " + Library.single_quote(id.toString))
 | |
| 71383 | 524 | def the_session(id: UUID.T): Headless.Session = _sessions.value.getOrElse(id, err_session(id)) | 
| 73340 | 525 | def add_session(entry: (UUID.T, Headless.Session)): Unit = _sessions.change(_ + entry) | 
| 69458 | 526 | def remove_session(id: UUID.T): Headless.Session = | 
| 67871 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 527 |   {
 | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 528 | _sessions.change_result(sessions => | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 529 |       sessions.get(id) match {
 | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 530 | case Some(session) => (session, sessions - id) | 
| 67883 | 531 | case None => err_session(id) | 
| 67871 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 532 | }) | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 533 | } | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 534 | |
| 73340 | 535 | def shutdown(): Unit = | 
| 67902 | 536 |   {
 | 
| 73367 | 537 | server.socket.close() | 
| 67791 | 538 | |
| 67902 | 539 | val sessions = _sessions.change_result(sessions => (sessions, Map.empty)) | 
| 540 |     for ((_, session) <- sessions) {
 | |
| 541 |       try {
 | |
| 542 | val result = session.stop() | |
| 71747 | 543 |         if (!result.ok) log("Session shutdown failed: " + result.print_rc)
 | 
| 67902 | 544 | } | 
| 545 |       catch { case ERROR(msg) => log("Session shutdown failed: " + msg) }
 | |
| 546 | } | |
| 547 | } | |
| 67791 | 548 | |
| 73340 | 549 |   override def join: Unit = { super.join; shutdown() }
 | 
| 66350 | 550 | |
| 73340 | 551 | override def handle(connection: Server.Connection): Unit = | 
| 66350 | 552 |   {
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 553 | using(new Server.Context(server, connection))(context => | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 554 |     {
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 555 | connection.reply_ok( | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 556 | JSON.Object( | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 557 | "isabelle_id" -> Isabelle_System.isabelle_id(), | 
| 73523 
2cd23d587db9
further clarification of Isabelle distribution identification -- avoid odd patching of sources;
 wenzelm parents: 
73367diff
changeset | 558 | "isabelle_name" -> Isabelle_System.isabelle_name())) | 
| 67867 | 559 | |
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 560 | var finished = false | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 561 |       while (!finished) {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 562 |         connection.read_message() match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 563 | case None => finished = true | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 564 |           case Some("") => context.notify("Command 'help' provides list of commands")
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 565 | case Some(msg) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 566 | val (name, argument) = Server.Argument.split(msg) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 567 |             Server.command_table.get(name) match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 568 | case Some(cmd) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 569 |                 argument match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 570 | case Server.Argument(arg) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 571 |                     if (cmd.command_body.isDefinedAt((context, arg))) {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 572 |                       Exn.capture { cmd.command_body((context, arg)) } match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 573 | case Exn.Res(task: Server.Task) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 574 | connection.reply_ok(JSON.Object(task.ident)) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 575 | task.start | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 576 | case Exn.Res(res) => connection.reply_ok(res) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 577 | case Exn.Exn(exn) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 578 | val err = Server.json_error(exn) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 579 | if (err.isEmpty) throw exn else connection.reply_error(err) | 
| 69464 | 580 | } | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 581 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 582 |                     else {
 | 
| 69464 | 583 | connection.reply_error_message( | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 584 | "Bad argument for command " + Library.single_quote(name), | 
| 69464 | 585 | "argument" -> argument) | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 586 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 587 | case _ => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 588 | connection.reply_error_message( | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 589 | "Malformed argument for command " + Library.single_quote(name), | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 590 | "argument" -> argument) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 591 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 592 |               case None => connection.reply_error("Bad command " + Library.single_quote(name))
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 593 | } | 
| 69464 | 594 | } | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 595 | } | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 596 | }) | 
| 66350 | 597 | } | 
| 66347 | 598 | } |