4 Copyright 2004 University of Cambridge
7 (* The watcher process starts a resolution process when it receives a *)
8 (* message from Isabelle *)
9 (* Signals Isabelle, puts output of child into pipe to Isabelle, *)
10 (* and removes dead processes. Also possible to kill all the resolution *)
11 (* processes currently running. *)
16 (* Send request to Watcher for multiple spasses to be called for filenames in arg *)
17 (* callResProvers (outstreamtoWatcher, prover name,prover-command, (settings,file) list *)
19 val callResProvers : TextIO.outstream * (string*string*string*string) list -> unit
21 (* Send message to watcher to kill resolution provers *)
22 val callSlayer : TextIO.outstream -> unit
24 (* Start a watcher and set up signal handlers *)
26 thm * (ResClause.clause * thm) Array.array ->
27 TextIO.instream * TextIO.outstream * Posix.Process.pid
28 val killWatcher : Posix.Process.pid -> unit
29 val setting_sep : char
34 structure Watcher: WATCHER =
37 (*Field separators, used to pack items onto a text line*)
38 val command_sep = #"\t"
39 and setting_sep = #"%";
41 val goals_being_watched = ref 0;
43 val trace_path = Path.basic "watcher_trace";
45 fun trace s = if !Output.show_debug_msgs then File.append (File.tmp_path trace_path) s
48 (*Representation of a watcher process*)
49 type proc = {pid : Posix.Process.pid,
50 instr : TextIO.instream,
51 outstr : TextIO.outstream};
53 (*Representation of a child (ATP) process*)
55 prover: string, (* Name of the resolution prover used, e.g. "spass"*)
56 file: string, (* The file containing the goal for the ATP to prove *)
57 proc_handle : (TextIO.instream,TextIO.outstream) Unix.proc,
58 instr : TextIO.instream, (*Output of the child process *)
59 outstr : TextIO.outstream}; (*Input to the child process *)
62 fun fdReader (name : string, fd : Posix.IO.file_desc) =
63 Posix.IO.mkTextReader {initBlkMode = true,name = name,fd = fd };
65 fun fdWriter (name, fd) =
66 Posix.IO.mkTextWriter {
73 fun openOutFD (name, fd) =
75 TextIO.StreamIO.mkOutstream (
76 fdWriter (name, fd), IO.BLOCK_BUF));
78 fun openInFD (name, fd) =
80 TextIO.StreamIO.mkInstream (
81 fdReader (name, fd), ""));
84 (* Send request to Watcher for a vampire to be called for filename in arg*)
85 fun callResProver (toWatcherStr, arg) =
86 (TextIO.output (toWatcherStr, arg^"\n");
87 TextIO.flushOut toWatcherStr)
89 (* Send request to Watcher for multiple provers to be called*)
90 fun callResProvers (toWatcherStr, []) =
91 (TextIO.output (toWatcherStr, "End of calls\n"); TextIO.flushOut toWatcherStr)
92 | callResProvers (toWatcherStr,
93 (prover,proverCmd,settings,probfile) :: args) =
94 (trace (space_implode ", " (["\ncallResProvers:", prover, proverCmd, probfile]));
95 (*Uses a special character to separate items sent to watcher*)
96 TextIO.output (toWatcherStr,
97 space_implode (str command_sep) [prover, proverCmd, settings, probfile, "\n"]);
98 goals_being_watched := (!goals_being_watched) + 1;
99 TextIO.flushOut toWatcherStr;
100 callResProvers (toWatcherStr,args))
103 (*Send message to watcher to kill currently running vampires. NOT USED and possibly
104 buggy. Note that killWatcher kills the entire process group anyway.*)
105 fun callSlayer toWatcherStr = (TextIO.output (toWatcherStr, "Kill children\n");
106 TextIO.flushOut toWatcherStr)
109 (* Get commands from Isabelle*)
110 fun getCmds (toParentStr, fromParentStr, cmdList) =
111 let val thisLine = TextIO.inputLine fromParentStr
113 trace("\nGot command from parent: " ^ thisLine);
114 if thisLine = "End of calls\n" orelse thisLine = "" then cmdList
115 else if thisLine = "Kill children\n"
116 then (TextIO.output (toParentStr,thisLine);
117 TextIO.flushOut toParentStr;
118 [("","Kill children",[],"")])
120 let val [prover,proverCmd,settingstr,probfile,_] =
121 String.tokens (fn c => c = command_sep) thisLine
122 val settings = String.tokens (fn c => c = setting_sep) settingstr
124 trace ("\nprover: " ^ prover ^ " prover path: " ^ proverCmd ^
125 "\n problem file: " ^ probfile);
126 getCmds (toParentStr, fromParentStr,
127 (prover, proverCmd, settings, probfile)::cmdList)
130 (trace "getCmds: command parsing failed!";
131 getCmds (toParentStr, fromParentStr, cmdList))
135 (*Get Io-descriptor for polling of an input stream*)
136 fun getInIoDesc someInstr =
137 let val (rd, buf) = TextIO.StreamIO.getReader(TextIO.getInstream someInstr)
138 val _ = TextIO.output (TextIO.stdOut, buf)
141 of TextPrimIO.RD{ioDesc = SOME iod, ...} => SOME iod
143 in (* since getting the reader will have terminated the stream, we need
144 * to build a new stream. *)
145 TextIO.setInstream(someInstr, TextIO.StreamIO.mkInstream(rd, buf));
149 fun pollChild fromStr =
150 case getInIoDesc fromStr of
152 (case OS.IO.pollDesc iod of
154 let val pd' = OS.IO.pollIn pd in
155 case OS.IO.poll ([pd'], SOME (Time.fromSeconds 2)) of
157 | pd''::_ => OS.IO.isIn pd''
163 (*************************************)
164 (* Set up a Watcher Process *)
165 (*************************************)
167 fun killChild proc = (Unix.kill(proc, Posix.Signal.kill); Unix.reap proc);
169 val killChildren = List.app (ignore o killChild o #proc_handle) : cmdproc list -> unit;
171 fun killWatcher (toParentStr, procList) =
172 (trace "\nWatcher timeout: Killing proof processes";
173 TextIO.output(toParentStr, "Timeout: Killing proof processes!\n");
174 TextIO.flushOut toParentStr;
175 killChildren procList;
176 Posix.Process.exit 0w0);
178 (* take an instream and poll its underlying reader for input *)
179 fun pollParentInput (fromParentIOD, fromParentStr, toParentStr) =
180 case OS.IO.pollDesc fromParentIOD of
182 (case OS.IO.poll ([OS.IO.pollIn pd], SOME (Time.fromSeconds 2)) of
184 | pd''::_ => if OS.IO.isIn pd''
185 then SOME (getCmds (toParentStr, fromParentStr, []))
189 (*get the number of the subgoal from the filename: the last digit string*)
190 fun number_from_filename s =
191 case String.tokens (not o Char.isDigit) s of
192 [] => (trace ("\nWatcher could not read subgoal nunber! " ^ s);
194 | numbers => valOf (Int.fromString (List.last numbers));
196 (* call ATP. Settings should be a list of strings ["-t300", "-m100000"]*)
197 fun execCmds [] procList = procList
198 | execCmds ((prover,proverCmd,settings,file)::cmds) procList =
199 let val _ = trace ("\nAbout to execute command: " ^ proverCmd ^ " " ^ file)
200 val childhandle:(TextIO.instream,TextIO.outstream) Unix.proc =
201 Unix.execute(proverCmd, settings@[file])
202 val (instr, outstr) = Unix.streamsOf childhandle
203 val newProcList = {prover=prover, file=file, proc_handle=childhandle,
204 instr=instr, outstr=outstr} :: procList
205 val _ = trace ("\nFinished at " ^
206 Date.toString(Date.fromTimeLocal(Time.now())))
207 in execCmds cmds newProcList end
209 fun checkChildren (th, clause_arr, toParentStr, children) =
210 let fun check [] = [] (* no children to check *)
211 | check (child::children) =
212 let val {prover, file, proc_handle, instr=childIn, ...} : cmdproc =
214 val _ = trace ("\nprobfile = " ^ file)
215 val sgno = number_from_filename file
216 val ppid = Posix.ProcEnv.getppid()
219 then (* check here for prover label on child*)
220 let val _ = trace ("\nInput available from child: " ^ file)
221 val childDone = (case prover of
222 "vampire" => AtpCommunication.checkVampProofFound
223 (childIn, toParentStr, ppid, file, clause_arr)
224 | "E" => AtpCommunication.checkEProofFound
225 (childIn, toParentStr, ppid, file, clause_arr)
226 | "spass" => AtpCommunication.checkSpassProofFound
227 (childIn, toParentStr, ppid, file, th, sgno,clause_arr)
228 | _ => (trace ("\nBad prover! " ^ prover); true) )
230 if childDone (*child has found a proof and transferred it*)
231 then (Unix.reap proc_handle; OS.FileSys.remove file;
233 else child :: check children
235 else (trace "\nNo child output"; child :: check children)
238 trace ("\nIn checkChildren, length of queue: " ^ Int.toString(length children));
243 fun setupWatcher (th,clause_arr) =
245 val p1 = Posix.IO.pipe() (*pipes for communication between parent and watcher*)
246 val p2 = Posix.IO.pipe()
247 (****** fork a watcher process and get it set up and going ******)
248 fun startWatcher procList =
249 case Posix.Process.fork() of
250 SOME pid => pid (* parent - i.e. main Isabelle process *)
252 let (* child - i.e. watcher *)
253 val oldchildin = #infd p1
254 val fromParent = Posix.FileSys.wordToFD 0w0
255 val oldchildout = #outfd p2
256 val toParent = Posix.FileSys.wordToFD 0w1
257 val fromParentIOD = Posix.FileSys.fdToIOD fromParent
258 val fromParentStr = openInFD ("_exec_in_parent", fromParent)
259 val toParentStr = openOutFD ("_exec_out_parent", toParent)
260 val pid = Posix.ProcEnv.getpid()
261 val () = Posix.ProcEnv.setpgid {pid = SOME pid, pgid = SOME pid}
262 (*set process group id: allows killing all children*)
263 val () = trace "\nsubgoals forked to startWatcher"
264 val limit = ref 200; (*don't let watcher run forever*)
265 (*Watcher Loop : Check running ATP processes for output*)
266 fun keepWatching procList =
267 (trace ("\npollParentInput. Limit = " ^ Int.toString (!limit) ^
268 " length(procList) = " ^ Int.toString(length procList));
269 OS.Process.sleep (Time.fromMilliseconds 100); limit := !limit - 1;
270 if !limit < 0 then killWatcher (toParentStr, procList)
272 case pollParentInput(fromParentIOD, fromParentStr, toParentStr) of
273 SOME [(_,"Kill children",_,_)] =>
274 (trace "\nReceived Kill command";
275 killChildren procList; keepWatching [])
276 | SOME cmds => (* deal with commands from Isabelle process *)
277 if length procList < 40 then (* Execute locally *)
278 let val _ = trace("\nCommands from parent: " ^
279 Int.toString(length cmds))
280 val newProcList' = checkChildren(th, clause_arr, toParentStr,
281 execCmds cmds procList)
282 in trace "\nCommands executed"; keepWatching newProcList' end
283 else (* Execute remotely [FIXME: NOT REALLY] *)
284 let val newProcList' = checkChildren (th, clause_arr, toParentStr,
285 execCmds cmds procList)
286 in keepWatching newProcList' end
287 | NONE => (* No new input from Isabelle *)
288 (trace "\nNothing from parent...";
289 keepWatching(checkChildren(th, clause_arr, toParentStr, procList))))
290 handle exn => (*FIXME: exn handler is too general!*)
291 (trace ("\nkeepWatching exception handler: " ^ Toplevel.exn_message exn);
292 keepWatching procList);
294 (*** Sort out pipes ********)
295 Posix.IO.close (#outfd p1); Posix.IO.close (#infd p2);
296 Posix.IO.dup2{old = oldchildin, new = fromParent};
297 Posix.IO.close oldchildin;
298 Posix.IO.dup2{old = oldchildout, new = toParent};
299 Posix.IO.close oldchildout;
300 keepWatching (procList)
303 val _ = TextIO.flushOut TextIO.stdOut
304 val pid = startWatcher []
305 (* communication streams to watcher*)
306 val instr = openInFD ("_exec_in", #infd p2)
307 val outstr = openOutFD ("_exec_out", #outfd p1)
309 (* close the child-side fds*)
310 Posix.IO.close (#outfd p2); Posix.IO.close (#infd p1);
311 (* set the fds close on exec *)
312 Posix.IO.setfd (#infd p2, Posix.IO.FD.flags [Posix.IO.FD.cloexec]);
313 Posix.IO.setfd (#outfd p1, Posix.IO.FD.flags [Posix.IO.FD.cloexec]);
314 {pid = pid, instr = instr, outstr = outstr}
319 (**********************************************************)
320 (* Start a watcher and set up signal handlers *)
321 (**********************************************************)
323 fun reapAll s = (*Signal handler to tidy away dead processes*)
324 (case Posix.Process.waitpid_nh(Posix.Process.W_ANY_CHILD, []) of
325 SOME _ => reapAll s | NONE => ())
326 handle OS.SysErr _ => ()
328 (*FIXME: does the main process need something like this?
329 IsaSignal.signal (IsaSignal.chld, IsaSignal.SIG_HANDLE reap)??*)
331 fun killWatcher pid =
332 (goals_being_watched := 0;
333 Posix.Process.kill(Posix.Process.K_GROUP pid, Posix.Signal.kill);
336 fun reapWatcher(pid, instr, outstr) = ignore
337 (TextIO.closeIn instr; TextIO.closeOut outstr;
338 Posix.Process.waitpid(Posix.Process.W_CHILD pid, []))
339 handle OS.SysErr _ => ()
341 fun string_of_subgoal th i =
342 string_of_cterm (List.nth(cprems_of th, i-1))
343 handle Subscript => "Subgoal number out of range!"
345 fun prems_string_of th = space_implode "\n" (map string_of_cterm (cprems_of th))
347 fun createWatcher (th, clause_arr) =
348 let val {pid=childpid, instr=childin, outstr=childout} = setupWatcher (th,clause_arr)
350 (goals_being_watched := !goals_being_watched - 1;
351 if !goals_being_watched = 0
353 (Output.debug ("\nReaping a watcher, childpid = " ^ string_of_pid childpid);
354 killWatcher childpid (*???; reapWatcher (childpid, childin, childout)*) )
356 val _ = Output.debug ("subgoals forked to createWatcher: "^ prems_string_of th);
358 let val outcome = TextIO.inputLine childin
359 val probfile = TextIO.inputLine childin
360 val sgno = number_from_filename probfile
361 val text = string_of_subgoal th sgno
362 val _ = Output.debug ("In signal handler. outcome = \"" ^ outcome ^
363 "\"\nprobfile = " ^ probfile ^
364 "\nGoals being watched: "^ Int.toString (!goals_being_watched))
366 if String.isPrefix "[" outcome (*indicates a proof reconstruction*)
367 then (priority (Recon_Transfer.apply_res_thm outcome);
369 else if String.isPrefix "Invalid" outcome
370 then (priority ("Subgoal is not provable:\n" ^ text);
372 else if String.isPrefix "Failure" outcome
373 then (priority ("Proof attempt failed:\n" ^ text);
375 (* print out a list of rules used from clasimpset*)
376 else if String.isPrefix "Success" outcome
377 then (priority (outcome ^ text);
379 (* if proof translation failed *)
380 else if String.isPrefix "Translation failed" outcome
381 then (priority (outcome ^ text);
383 else (priority "System error in proof handler";
386 in IsaSignal.signal (IsaSignal.usr2, IsaSignal.SIG_HANDLE proofHandler);
387 (childin, childout, childpid)
390 end (* structure Watcher *)