5 Isabelle process management -- always reactive due to multi-threaded I/O. |
5 Isabelle process management -- always reactive due to multi-threaded I/O. |
6 */ |
6 */ |
7 |
7 |
8 package isabelle |
8 package isabelle |
9 |
9 |
10 import java.util.Properties |
|
11 import java.util.concurrent.LinkedBlockingQueue |
10 import java.util.concurrent.LinkedBlockingQueue |
12 import java.io.{BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter, |
11 import java.io.{BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter, |
13 InputStream, OutputStream, IOException} |
12 InputStream, OutputStream, IOException} |
14 |
13 |
15 |
14 |
78 kind == SIGNAL || |
77 kind == SIGNAL || |
79 kind == EXIT || |
78 kind == EXIT || |
80 kind == STATUS |
79 kind == STATUS |
81 } |
80 } |
82 |
81 |
83 class Result(val kind: Kind.Value, val props: Properties, val result: String) { |
82 class Result(val kind: Kind.Value, val props: List[(String, String)], val result: String) { |
84 override def toString = { |
83 override def toString = { |
85 val trees = YXML.parse_body_failsafe(result) |
84 val trees = YXML.parse_body_failsafe(result) |
86 val res = |
85 val res = |
87 if (kind == Kind.STATUS) trees.map(_.toString).mkString |
86 if (kind == Kind.STATUS) trees.map(_.toString).mkString |
88 else trees.flatMap(XML.content(_).mkString).mkString |
87 else trees.flatMap(XML.content(_).mkString).mkString |
89 if (props == null) kind.toString + " [[" + res + "]]" |
88 if (props.isEmpty) |
90 else kind.toString + " " + props.toString + " [[" + res + "]]" |
89 kind.toString + " [[" + res + "]]" |
|
90 else |
|
91 kind.toString + " " + |
|
92 (for ((x, y) <- props) yield x + "=" + y).mkString("{", ",", "}") + " [[" + res + "]]" |
91 } |
93 } |
92 def is_raw = Kind.is_raw(kind) |
94 def is_raw = Kind.is_raw(kind) |
93 def is_control = Kind.is_control(kind) |
95 def is_control = Kind.is_control(kind) |
94 def is_system = Kind.is_system(kind) |
96 def is_system = Kind.is_system(kind) |
95 } |
97 } |
96 |
98 |
97 def parse_message(kind: Kind.Value, result: String) = { |
99 def parse_message(isabelle_system: IsabelleSystem, result: Result) = |
98 XML.Elem(Markup.MESSAGE, List((Markup.CLASS, Kind.markup(kind))), |
100 { |
99 YXML.parse_body_failsafe(result)) |
101 XML.Elem(Markup.MESSAGE, (Markup.CLASS, Kind.markup(result.kind)) :: result.props, |
|
102 YXML.parse_body_failsafe(isabelle_system.symbols.decode(result.result))) |
100 } |
103 } |
101 } |
104 } |
102 |
105 |
103 |
106 |
104 class IsabelleProcess(isabelle_system: IsabelleSystem, |
107 class IsabelleProcess(isabelle_system: IsabelleSystem, |
117 |
120 |
118 private var proc: Process = null |
121 private var proc: Process = null |
119 private var closing = false |
122 private var closing = false |
120 private var pid: String = null |
123 private var pid: String = null |
121 private var the_session: String = null |
124 private var the_session: String = null |
122 def session() = the_session |
125 def session = the_session |
123 |
126 |
124 |
127 |
125 /* results */ |
128 /* results */ |
126 |
129 |
|
130 def parse_message(result: Result): XML.Tree = |
|
131 IsabelleProcess.parse_message(isabelle_system, result) |
|
132 |
127 private val result_queue = new LinkedBlockingQueue[Result] |
133 private val result_queue = new LinkedBlockingQueue[Result] |
128 |
134 |
129 private def put_result(kind: Kind.Value, props: Properties, result: String) { |
135 private def put_result(kind: Kind.Value, props: List[(String, String)], result: String) |
130 if (kind == Kind.INIT && props != null) { |
136 { |
131 pid = props.getProperty(Markup.PID) |
137 if (kind == Kind.INIT) { |
132 the_session = props.getProperty(Markup.SESSION) |
138 val map = Map(props: _*) |
|
139 if (map.isDefinedAt(Markup.PID)) pid = map(Markup.PID) |
|
140 if (map.isDefinedAt(Markup.SESSION)) the_session = map(Markup.SESSION) |
133 } |
141 } |
134 result_queue.put(new Result(kind, props, result)) |
142 result_queue.put(new Result(kind, props, result)) |
135 } |
143 } |
136 |
144 |
137 private class ResultThread extends Thread("isabelle: results") { |
145 private class ResultThread extends Thread("isabelle: results") { |
141 val result = |
149 val result = |
142 try { result_queue.take } |
150 try { result_queue.take } |
143 catch { case _: NullPointerException => null } |
151 catch { case _: NullPointerException => null } |
144 |
152 |
145 if (result != null) { |
153 if (result != null) { |
146 results.event(result) // FIXME try/catch (!??) |
154 results.event(result) |
147 if (result.kind == Kind.EXIT) finished = true |
155 if (result.kind == Kind.EXIT) finished = true |
148 } |
156 } |
149 else finished = true |
157 else finished = true |
150 } |
158 } |
151 } |
159 } |
154 |
162 |
155 /* signals */ |
163 /* signals */ |
156 |
164 |
157 def interrupt() = synchronized { |
165 def interrupt() = synchronized { |
158 if (proc == null) error("Cannot interrupt Isabelle: no process") |
166 if (proc == null) error("Cannot interrupt Isabelle: no process") |
159 if (pid == null) put_result(Kind.SYSTEM, null, "Cannot interrupt: unknown pid") |
167 if (pid == null) put_result(Kind.SYSTEM, Nil, "Cannot interrupt: unknown pid") |
160 else { |
168 else { |
161 try { |
169 try { |
162 if (isabelle_system.execute(true, "kill", "-INT", pid).waitFor == 0) |
170 if (isabelle_system.execute(true, "kill", "-INT", pid).waitFor == 0) |
163 put_result(Kind.SIGNAL, null, "INT") |
171 put_result(Kind.SIGNAL, Nil, "INT") |
164 else |
172 else |
165 put_result(Kind.SYSTEM, null, "Cannot interrupt: kill command failed") |
173 put_result(Kind.SYSTEM, Nil, "Cannot interrupt: kill command failed") |
166 } |
174 } |
167 catch { case e: IOException => error("Cannot interrupt Isabelle: " + e.getMessage) } |
175 catch { case e: IOException => error("Cannot interrupt Isabelle: " + e.getMessage) } |
168 } |
176 } |
169 } |
177 } |
170 |
178 |
171 def kill() = synchronized { |
179 def kill() = synchronized { |
172 if (proc == 0) error("Cannot kill Isabelle: no process") |
180 if (proc == 0) error("Cannot kill Isabelle: no process") |
173 else { |
181 else { |
174 try_close() |
182 try_close() |
175 Thread.sleep(500) |
183 Thread.sleep(500) |
176 put_result(Kind.SIGNAL, null, "KILL") |
184 put_result(Kind.SIGNAL, Nil, "KILL") |
177 proc.destroy |
185 proc.destroy |
178 proc = null |
186 proc = null |
179 pid = null |
187 pid = null |
180 } |
188 } |
181 } |
189 } |
196 |
204 |
197 |
205 |
198 def command(text: String) = |
206 def command(text: String) = |
199 output_sync("Isabelle.command " + IsabelleSyntax.encode_string(text)) |
207 output_sync("Isabelle.command " + IsabelleSyntax.encode_string(text)) |
200 |
208 |
201 def command(props: Properties, text: String) = |
209 def command(props: List[(String, String)], text: String) = |
202 output_sync("Isabelle.command " + IsabelleSyntax.encode_properties(props) + " " + |
210 output_sync("Isabelle.command " + IsabelleSyntax.encode_properties(props) + " " + |
203 IsabelleSyntax.encode_string(text)) |
211 IsabelleSyntax.encode_string(text)) |
204 |
212 |
205 def ML(text: String) = |
213 def ML(text: String) = |
206 output_sync("ML_val " + IsabelleSyntax.encode_string(text)) |
214 output_sync("ML_val " + IsabelleSyntax.encode_string(text)) |
231 if (s == "\u0000") { |
239 if (s == "\u0000") { |
232 writer.close |
240 writer.close |
233 finished = true |
241 finished = true |
234 } |
242 } |
235 else { |
243 else { |
236 put_result(Kind.STDIN, null, s) |
244 put_result(Kind.STDIN, Nil, s) |
237 writer.write(s) |
245 writer.write(s) |
238 writer.flush |
246 writer.flush |
239 } |
247 } |
240 //}}} |
248 //}}} |
241 } |
249 } |
242 catch { |
250 catch { |
243 case e: IOException => put_result(Kind.SYSTEM, null, "Stdin thread: " + e.getMessage) |
251 case e: IOException => put_result(Kind.SYSTEM, Nil, "Stdin thread: " + e.getMessage) |
244 } |
252 } |
245 } |
253 } |
246 put_result(Kind.SYSTEM, null, "Stdin thread terminated") |
254 put_result(Kind.SYSTEM, Nil, "Stdin thread terminated") |
247 } |
255 } |
248 } |
256 } |
249 |
257 |
250 |
258 |
251 /* stdout */ |
259 /* stdout */ |
265 c = reader.read |
273 c = reader.read |
266 if (c >= 0) result.append(c.asInstanceOf[Char]) |
274 if (c >= 0) result.append(c.asInstanceOf[Char]) |
267 else done = true |
275 else done = true |
268 } |
276 } |
269 if (result.length > 0) { |
277 if (result.length > 0) { |
270 put_result(Kind.STDOUT, null, result.toString) |
278 put_result(Kind.STDOUT, Nil, result.toString) |
271 result.length = 0 |
279 result.length = 0 |
272 } |
280 } |
273 else { |
281 else { |
274 reader.close |
282 reader.close |
275 finished = true |
283 finished = true |
276 try_close() |
284 try_close() |
277 } |
285 } |
278 //}}} |
286 //}}} |
279 } |
287 } |
280 catch { |
288 catch { |
281 case e: IOException => put_result(Kind.SYSTEM, null, "Stdout thread: " + e.getMessage) |
289 case e: IOException => put_result(Kind.SYSTEM, Nil, "Stdout thread: " + e.getMessage) |
282 } |
290 } |
283 } |
291 } |
284 put_result(Kind.SYSTEM, null, "Stdout thread terminated") |
292 put_result(Kind.SYSTEM, Nil, "Stdout thread terminated") |
285 } |
293 } |
286 } |
294 } |
287 |
295 |
288 |
296 |
289 /* messages */ |
297 /* messages */ |
290 |
298 |
291 private class MessageThread(fifo: String) extends Thread("isabelle: messages") { |
299 private class MessageThread(fifo: String) extends Thread("isabelle: messages") { |
292 override def run() = { |
300 override def run() = { |
293 val reader = isabelle_system.fifo_reader(fifo) |
301 val reader = isabelle_system.fifo_reader(fifo) |
294 var kind: Kind.Value = null |
302 var kind: Kind.Value = null |
295 var props: Properties = null |
303 var props: List[(String, String)] = Nil |
296 var result = new StringBuilder |
304 var result = new StringBuilder |
297 |
305 |
298 var finished = false |
306 var finished = false |
299 while (!finished) { |
307 while (!finished) { |
300 try { |
308 try { |
305 c = reader.read |
313 c = reader.read |
306 if (c >= 0 && c != 2) result.append(c.asInstanceOf[Char]) |
314 if (c >= 0 && c != 2) result.append(c.asInstanceOf[Char]) |
307 } while (c >= 0 && c != 2) |
315 } while (c >= 0 && c != 2) |
308 |
316 |
309 if (result.length > 0) { |
317 if (result.length > 0) { |
310 put_result(Kind.SYSTEM, null, "Malformed message:\n" + result.toString) |
318 put_result(Kind.SYSTEM, Nil, "Malformed message:\n" + result.toString) |
311 result.length = 0 |
319 result.length = 0 |
312 } |
320 } |
313 if (c < 0) { |
321 if (c < 0) { |
314 reader.close |
322 reader.close |
315 finished = true |
323 finished = true |
337 if (line.endsWith("\u0002,")) { |
345 if (line.endsWith("\u0002,")) { |
338 val i = line.indexOf('=') |
346 val i = line.indexOf('=') |
339 if (i > 0) { |
347 if (i > 0) { |
340 val name = line.substring(0, i) |
348 val name = line.substring(0, i) |
341 val value = line.substring(i + 1, len - 2) |
349 val value = line.substring(i + 1, len - 2) |
342 if (props == null) props = new Properties |
350 props = (name, value) :: props |
343 if (!props.containsKey(name)) props.setProperty(name, value) |
|
344 } |
351 } |
345 } |
352 } |
346 // last text line |
353 // last text line |
347 else if (line.endsWith("\u0002.")) { |
354 else if (line.endsWith("\u0002.")) { |
348 result.append(line.substring(0, len - 2)) |
355 result.append(line.substring(0, len - 2)) |
349 put_result(kind, props, result.toString) |
356 put_result(kind, props.reverse, result.toString) |
350 result.length = 0 |
357 result.length = 0 |
351 kind = null |
358 kind = null |
352 } |
359 } |
353 // text line |
360 // text line |
354 else { |
361 else { |
375 /* isabelle version */ |
382 /* isabelle version */ |
376 |
383 |
377 { |
384 { |
378 val (msg, rc) = isabelle_system.isabelle_tool("version") |
385 val (msg, rc) = isabelle_system.isabelle_tool("version") |
379 if (rc != 0) error("Version check failed -- bad Isabelle installation:\n" + msg) |
386 if (rc != 0) error("Version check failed -- bad Isabelle installation:\n" + msg) |
380 put_result(Kind.SYSTEM, null, msg) |
387 put_result(Kind.SYSTEM, Nil, msg) |
381 } |
388 } |
382 |
389 |
383 |
390 |
384 /* messages */ |
391 /* messages */ |
385 |
392 |
416 |
423 |
417 new Thread("isabelle: exit") { |
424 new Thread("isabelle: exit") { |
418 override def run() = { |
425 override def run() = { |
419 val rc = proc.waitFor() |
426 val rc = proc.waitFor() |
420 Thread.sleep(300) |
427 Thread.sleep(300) |
421 put_result(Kind.SYSTEM, null, "Exit thread terminated") |
428 put_result(Kind.SYSTEM, Nil, "Exit thread terminated") |
422 put_result(Kind.EXIT, null, Integer.toString(rc)) |
429 put_result(Kind.EXIT, Nil, Integer.toString(rc)) |
423 rm_fifo() |
430 rm_fifo() |
424 } |
431 } |
425 }.start |
432 }.start |
426 |
433 |
427 } |
434 } |