author | wenzelm |
Sat, 29 Dec 2007 17:47:12 +0100 | |
changeset 25745 | 3f86e9dc3860 |
parent 25662 | 8c45834392e7 |
child 25747 | be58ef74140a |
permissions | -rw-r--r-- |
25648 | 1 |
/* |
2 |
* $Id$ |
|
3 |
*/ |
|
4 |
||
5 |
package isabelle; |
|
6 |
||
7 |
import java.io.*; |
|
25658
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
8 |
import java.util.ArrayList; |
25648 | 9 |
import java.util.Locale; |
10 |
import java.util.concurrent.LinkedBlockingQueue; |
|
11 |
||
25745 | 12 |
/** |
13 |
* Posix process wrapper for Isabelle (see also <code>src/Pure/Tools/isabelle_process.ML</code>). |
|
14 |
* <p> |
|
15 |
* |
|
16 |
* The process model: |
|
17 |
* <p> |
|
18 |
* |
|
19 |
* <ol> |
|
20 |
* <li> input |
|
21 |
* <ul> |
|
22 |
* <li> stdin stream |
|
23 |
* <li> signals (interrupt, kill) |
|
24 |
* </ul> |
|
25 |
* |
|
26 |
* <li> output/results |
|
27 |
* <ul> |
|
28 |
* <li> stdout stream, interspersed with marked Isabelle messages |
|
29 |
* <li> stderr stream |
|
30 |
* <li> process exit (return code) |
|
31 |
* </ul> |
|
32 |
* </ol> |
|
33 |
* |
|
34 |
* I/O is fully asynchronous, with unrestricted buffers. Text is encoded as UTF-8. |
|
35 |
* <p> |
|
36 |
* |
|
37 |
* System properties: |
|
38 |
* <p> |
|
39 |
* |
|
40 |
* <dl> |
|
41 |
* <dt> <code>isabelle.home</code> <di> <code>ISABELLE_HOME</code> of Isabelle installation |
|
42 |
* (default determined from isabelle-process via <code>PATH</code>) |
|
43 |
* <dt> <code>isabelle.shell</code> <di> optional shell command for <code>isabelle-process</code> (also requires isabelle.home) |
|
44 |
* <dt> <code>isabelle.kill</code> <di> optional kill command (default <code>kill</code>) |
|
45 |
*/ |
|
46 |
||
25648 | 47 |
public class IsabelleProcess { |
48 |
private volatile Process proc = null; |
|
49 |
private volatile String pid = null; |
|
50 |
private volatile boolean closing = false; |
|
51 |
private LinkedBlockingQueue<String> output = null; |
|
52 |
||
53 |
||
25745 | 54 |
/** |
55 |
* Models failures in process management. |
|
56 |
*/ |
|
25648 | 57 |
public static class IsabelleProcessException extends Exception { |
58 |
public IsabelleProcessException() { |
|
59 |
super(); |
|
60 |
} |
|
61 |
public IsabelleProcessException(String msg) { |
|
62 |
super(msg); |
|
63 |
} |
|
64 |
}; |
|
65 |
||
66 |
||
25745 | 67 |
/** |
68 |
* Models cooked results from the process. |
|
69 |
*/ |
|
25648 | 70 |
public static class Result { |
71 |
public enum Kind { |
|
72 |
STDIN, STDOUT, STDERR, SIGNAL, EXIT, // Posix channels/events |
|
73 |
WRITELN, PRIORITY, TRACING, WARNING, ERROR, DEBUG, // Isabelle messages |
|
74 |
SYSTEM // internal system notification |
|
75 |
}; |
|
76 |
public Kind kind; |
|
77 |
public String result; |
|
78 |
||
79 |
public Result(Kind kind, String result) { |
|
80 |
this.kind = kind; |
|
81 |
this.result = result; |
|
82 |
} |
|
83 |
||
84 |
public String toString() { |
|
85 |
return this.kind.toString() + " [[" + this.result + "]]"; |
|
86 |
} |
|
87 |
} |
|
88 |
||
25745 | 89 |
|
90 |
/** |
|
91 |
* Main result queue -- accumulates cooked messages asynchronously. |
|
92 |
*/ |
|
25648 | 93 |
public LinkedBlockingQueue<Result> results; |
94 |
||
95 |
private synchronized void putResult(Result.Kind kind, String result) { |
|
96 |
try { |
|
97 |
results.put(new Result(kind, result)); |
|
98 |
} catch (InterruptedException exn) { } |
|
99 |
} |
|
100 |
||
101 |
||
25745 | 102 |
/** |
103 |
* Interrupts Isabelle process via Posix signal. |
|
104 |
* @throws isabelle.IsabelleProcess.IsabelleProcessException |
|
105 |
*/ |
|
25648 | 106 |
public synchronized void interrupt() throws IsabelleProcessException |
107 |
{ |
|
108 |
if (proc != null && pid != null) { |
|
25661 | 109 |
String kill = System.getProperty("isabelle.kill", "kill"); |
110 |
String [] cmdline = {kill, "-INT", pid}; |
|
25648 | 111 |
try { |
112 |
putResult(Result.Kind.SIGNAL, "INT"); |
|
25661 | 113 |
int rc = Runtime.getRuntime().exec(cmdline).waitFor(); |
25648 | 114 |
if (rc != 0) { |
115 |
throw new IsabelleProcessException("Cannot interrupt: kill command failed"); |
|
116 |
} |
|
117 |
} catch (IOException exn) { |
|
118 |
throw new IsabelleProcessException(exn.getMessage()); |
|
119 |
} catch (InterruptedException exn) { |
|
120 |
throw new IsabelleProcessException("Cannot interrupt: aborted"); |
|
121 |
} |
|
122 |
} else { |
|
123 |
throw new IsabelleProcessException("Cannot interrupt: no process"); |
|
124 |
} |
|
125 |
} |
|
126 |
||
127 |
||
25745 | 128 |
/** |
129 |
* Kills Isabelle process: try close -- sleep -- destroy. |
|
130 |
*/ |
|
25648 | 131 |
public synchronized void kill() throws IsabelleProcessException |
132 |
{ |
|
133 |
if (proc != null) { |
|
134 |
tryClose(); |
|
135 |
try { |
|
136 |
Thread.sleep(300); |
|
137 |
} catch (InterruptedException exn) { } |
|
138 |
putResult(Result.Kind.SIGNAL, "KILL"); |
|
139 |
proc.destroy(); |
|
140 |
proc = null; |
|
141 |
} else { |
|
142 |
throw new IsabelleProcessException("Cannot kill: no process"); |
|
143 |
} |
|
144 |
} |
|
145 |
||
146 |
||
25745 | 147 |
/** |
148 |
* Auxiliary operation to encode text as Isabelle string token. |
|
149 |
*/ |
|
25648 | 150 |
public static String encodeString(String str) { |
151 |
Locale locale = null; |
|
152 |
StringBuffer buf = new StringBuffer(100); |
|
153 |
int i; |
|
154 |
char c; |
|
155 |
||
156 |
buf.append("\""); |
|
157 |
for (i = 0; i < str.length(); i++) { |
|
158 |
c = str.charAt(i); |
|
159 |
if (c < 32 || c == '\\' || c == '\"') { |
|
160 |
buf.append(String.format(locale, "\\%03d", (int) c)); |
|
161 |
} else { |
|
162 |
buf.append(c); |
|
163 |
} |
|
164 |
} |
|
165 |
buf.append("\""); |
|
166 |
return buf.toString(); |
|
167 |
} |
|
168 |
||
169 |
||
170 |
/* output being piped into the process (stdin) */ |
|
171 |
||
172 |
private volatile BufferedWriter outputWriter; |
|
173 |
private class OutputThread extends Thread |
|
174 |
{ |
|
175 |
public void run() |
|
176 |
{ |
|
177 |
while (outputWriter != null) { |
|
178 |
try { |
|
179 |
String s = output.take(); |
|
180 |
if (s.equals("\u0000")) { |
|
181 |
outputWriter.close(); |
|
182 |
outputWriter = null; |
|
183 |
} else { |
|
184 |
putResult(Result.Kind.STDIN, s); |
|
185 |
outputWriter.write(s); |
|
186 |
outputWriter.flush(); |
|
187 |
} |
|
188 |
} catch (InterruptedException exn) { |
|
189 |
putResult(Result.Kind.SYSTEM, "Output thread interrupted"); |
|
190 |
} catch (IOException exn) { |
|
191 |
putResult(Result.Kind.SYSTEM, exn.getMessage()); |
|
192 |
} |
|
193 |
} |
|
194 |
putResult(Result.Kind.SYSTEM, "Output thread terminated"); |
|
195 |
} |
|
196 |
} |
|
197 |
private OutputThread outputThread; |
|
198 |
||
199 |
||
200 |
// public operations |
|
201 |
||
25745 | 202 |
/** |
203 |
* Feeds arbitrary text into the process. |
|
204 |
*/ |
|
25648 | 205 |
public synchronized void output(String text) throws IsabelleProcessException |
206 |
{ |
|
207 |
if (proc != null && !closing) { |
|
208 |
try { |
|
209 |
output.put(text); |
|
210 |
} catch (InterruptedException ex) { |
|
211 |
throw new IsabelleProcessException("Cannot output: aborted"); |
|
212 |
} |
|
213 |
} else if (proc == null) { |
|
214 |
throw new IsabelleProcessException("Cannot output: no process"); |
|
215 |
} else { |
|
216 |
throw new IsabelleProcessException("Cannot output: already closing"); |
|
217 |
} |
|
218 |
} |
|
219 |
||
25745 | 220 |
/** |
221 |
* Closes Isabelle process input, causing termination eventually. |
|
222 |
*/ |
|
25648 | 223 |
public synchronized void close() throws IsabelleProcessException |
224 |
{ |
|
225 |
output("\u0000"); |
|
226 |
closing = true; |
|
227 |
// FIXME watchdog/timeout |
|
228 |
} |
|
229 |
||
25745 | 230 |
/** |
231 |
* Closes Isabelle process input, causing termination eventually -- unless process already terminated. |
|
232 |
*/ |
|
25648 | 233 |
public synchronized void tryClose() |
234 |
{ |
|
235 |
if (proc != null && !closing) { |
|
236 |
try { |
|
237 |
close(); |
|
238 |
} catch (IsabelleProcessException ex) { } |
|
239 |
} |
|
240 |
} |
|
241 |
||
242 |
private synchronized void outputSync(String text) throws IsabelleProcessException |
|
243 |
{ |
|
244 |
output(" \\<^sync>\n; " + text + " \\<^sync>;\n"); |
|
245 |
} |
|
246 |
||
25745 | 247 |
/** |
248 |
* Feeds exactly one Isabelle command into the process. |
|
249 |
*/ |
|
25648 | 250 |
public synchronized void command(String text) throws IsabelleProcessException |
251 |
{ |
|
252 |
outputSync("Isabelle.command " + encodeString(text)); |
|
253 |
} |
|
254 |
||
25745 | 255 |
/** |
256 |
* Feeds ML toplevel declarations into the process. |
|
257 |
*/ |
|
25648 | 258 |
public synchronized void ML(String text) throws IsabelleProcessException |
259 |
{ |
|
260 |
outputSync("ML " + encodeString(text)); |
|
261 |
} |
|
262 |
||
263 |
||
264 |
/* input from the process (stdout/stderr) */ |
|
265 |
||
266 |
private volatile BufferedReader inputReader; |
|
267 |
private class InputThread extends Thread |
|
268 |
{ |
|
269 |
public void run() |
|
270 |
{ |
|
271 |
Result.Kind kind = Result.Kind.STDOUT; |
|
272 |
StringBuffer buf = new StringBuffer(100); |
|
273 |
||
274 |
try { |
|
275 |
while (inputReader != null) { |
|
276 |
if (kind == Result.Kind.STDOUT && pid != null) { |
|
277 |
// char mode |
|
278 |
int c = -1; |
|
279 |
while ((buf.length() == 0 || inputReader.ready()) && |
|
280 |
(c = inputReader.read()) > 0 && c != 2) { |
|
281 |
buf.append((char) c); |
|
282 |
} |
|
283 |
if (buf.length() > 0) { |
|
284 |
putResult(kind, buf.toString()); |
|
285 |
buf = new StringBuffer(100); |
|
286 |
} |
|
287 |
if (c == 2) { |
|
288 |
c = inputReader.read(); |
|
289 |
switch (c) { |
|
290 |
case 'A': kind = Result.Kind.WRITELN; break; |
|
291 |
case 'B': kind = Result.Kind.PRIORITY; break; |
|
292 |
case 'C': kind = Result.Kind.TRACING; break; |
|
293 |
case 'D': kind = Result.Kind.WARNING; break; |
|
294 |
case 'E': kind = Result.Kind.ERROR; break; |
|
295 |
case 'F': kind = Result.Kind.DEBUG; break; |
|
296 |
default: kind = Result.Kind.STDOUT; break; |
|
297 |
} |
|
298 |
} |
|
299 |
if (c == -1) { |
|
300 |
inputReader.close(); |
|
301 |
inputReader = null; |
|
302 |
tryClose(); |
|
303 |
} |
|
304 |
} else { |
|
305 |
// line mode |
|
306 |
String line = null; |
|
307 |
if ((line = inputReader.readLine()) != null) { |
|
308 |
if (pid == null && kind == Result.Kind.STDOUT && line.startsWith("PID=")) { |
|
309 |
pid = line.substring("PID=".length()); |
|
310 |
} else if (kind == Result.Kind.STDOUT) { |
|
311 |
buf.append(line); |
|
312 |
buf.append("\n"); |
|
313 |
putResult(kind, buf.toString()); |
|
314 |
buf = new StringBuffer(100); |
|
315 |
} else { |
|
316 |
int len = line.length(); |
|
317 |
if (len >= 2 && line.charAt(len - 2) == 2 && line.charAt(len - 1) == '.') { |
|
318 |
buf.append(line.substring(0, len - 2)); |
|
319 |
putResult(kind, buf.toString()); |
|
320 |
buf = new StringBuffer(100); |
|
321 |
kind = Result.Kind.STDOUT; |
|
322 |
} else { |
|
323 |
buf.append(line); |
|
324 |
buf.append("\n"); |
|
325 |
} |
|
326 |
} |
|
327 |
} else { |
|
328 |
inputReader.close(); |
|
329 |
inputReader = null; |
|
330 |
tryClose(); |
|
331 |
} |
|
332 |
} |
|
333 |
} |
|
334 |
} catch (IOException exn) { |
|
335 |
putResult(Result.Kind.SYSTEM, exn.getMessage()); |
|
336 |
} |
|
337 |
putResult(Result.Kind.SYSTEM, "Input thread terminated"); |
|
338 |
} |
|
339 |
} |
|
340 |
private InputThread inputThread; |
|
341 |
||
342 |
private volatile BufferedReader errorReader; |
|
343 |
private class ErrorThread extends Thread |
|
344 |
{ |
|
345 |
public void run() |
|
346 |
{ |
|
347 |
try { |
|
348 |
while (errorReader != null) { |
|
349 |
StringBuffer buf = new StringBuffer(100); |
|
350 |
int c; |
|
351 |
while ((buf.length() == 0 || errorReader.ready()) && (c = errorReader.read()) > 0) { |
|
352 |
buf.append((char) c); |
|
353 |
} |
|
354 |
if (buf.length() > 0) { |
|
355 |
putResult(Result.Kind.STDERR, buf.toString()); |
|
356 |
} else { |
|
357 |
errorReader.close(); |
|
358 |
errorReader = null; |
|
359 |
tryClose(); |
|
360 |
} |
|
361 |
} |
|
362 |
} catch (IOException exn) { |
|
363 |
putResult(Result.Kind.SYSTEM, exn.getMessage()); |
|
364 |
} |
|
365 |
putResult(Result.Kind.SYSTEM, "Error thread terminated"); |
|
366 |
} |
|
367 |
} |
|
368 |
private ErrorThread errorThread; |
|
369 |
||
370 |
||
371 |
/* exit thread */ |
|
372 |
||
373 |
private class ExitThread extends Thread |
|
374 |
{ |
|
375 |
public void run() |
|
376 |
{ |
|
377 |
try { |
|
378 |
int rc = proc.waitFor(); |
|
25649
9fc75df32c81
ExitThread: sleep(300) before delivering EXIT message;
wenzelm
parents:
25648
diff
changeset
|
379 |
Thread.sleep(300); |
25654 | 380 |
putResult(Result.Kind.SYSTEM, "Exit thread terminated"); |
25648 | 381 |
putResult(Result.Kind.EXIT, Integer.toString(rc)); |
382 |
proc = null; |
|
383 |
} catch (InterruptedException exn) { |
|
384 |
putResult(Result.Kind.SYSTEM, "Exit thread interrupted"); |
|
385 |
} |
|
386 |
} |
|
387 |
} |
|
388 |
private ExitThread exitThread; |
|
389 |
||
390 |
||
25745 | 391 |
/** |
392 |
* Creates Isabelle process with specified logic image. |
|
393 |
*/ |
|
25648 | 394 |
public IsabelleProcess(String logic) throws IsabelleProcessException |
395 |
{ |
|
25658
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
396 |
ArrayList<String> cmdline = new ArrayList<String> (); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
397 |
String shell = null; |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
398 |
String home = null; |
25648 | 399 |
String charset = "UTF-8"; |
25659 | 400 |
|
25658
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
401 |
shell = System.getProperty("isabelle.shell"); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
402 |
home = System.getProperty("isabelle.home"); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
403 |
if (shell != null && home != null) { |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
404 |
cmdline.add(shell); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
405 |
cmdline.add(home + "/bin/isabelle-process"); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
406 |
} else if (home != null) { |
25659 | 407 |
cmdline.add(home + "/bin/isabelle-process"); |
25658
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
408 |
} else if (shell != null) { |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
409 |
throw new IsabelleProcessException("Cannot start process: isabelle.shell property requires isabelle.home"); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
410 |
} else { |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
411 |
cmdline.add("isabelle-process"); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
412 |
} |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
413 |
cmdline.add("-W"); |
25661 | 414 |
if (logic != null) cmdline.add(logic); |
25658
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
415 |
|
25648 | 416 |
try { |
25658
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
417 |
String [] cmd = new String[cmdline.size()]; |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
418 |
cmdline.toArray(cmd); |
c3ae6c345fb5
compose command line according to isabelle.shell/home system properties;
wenzelm
parents:
25654
diff
changeset
|
419 |
proc = Runtime.getRuntime().exec(cmd); |
25648 | 420 |
} catch (IOException exn) { |
421 |
throw new IsabelleProcessException(exn.getMessage()); |
|
422 |
} |
|
423 |
||
424 |
try { |
|
425 |
outputWriter = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream(), charset)); |
|
426 |
inputReader = new BufferedReader(new InputStreamReader(proc.getInputStream(), charset)); |
|
427 |
errorReader = new BufferedReader(new InputStreamReader(proc.getErrorStream(), charset)); |
|
428 |
} catch (UnsupportedEncodingException exn) { |
|
429 |
proc.destroy(); |
|
430 |
throw new Error(exn.getMessage()); |
|
431 |
} |
|
432 |
||
433 |
output = new LinkedBlockingQueue<String>(); |
|
434 |
outputThread = new OutputThread(); |
|
435 |
||
436 |
results = new LinkedBlockingQueue<Result>(); |
|
437 |
inputThread = new InputThread(); |
|
438 |
errorThread = new ErrorThread(); |
|
439 |
exitThread = new ExitThread(); |
|
440 |
||
441 |
outputThread.start(); |
|
442 |
inputThread.start(); |
|
443 |
errorThread.start(); |
|
444 |
exitThread.start(); |
|
445 |
} |
|
25661 | 446 |
|
25745 | 447 |
/** |
448 |
* Creates Isabelle process with default logic image. |
|
449 |
*/ |
|
25661 | 450 |
public IsabelleProcess() throws IsabelleProcessException { |
451 |
this(null); |
|
452 |
} |
|
25648 | 453 |
} |