Import Upstream version 20180207
[hcoop/debian/mlton.git] / lib / cml / cml-lib / trace-cml.sml
CommitLineData
7f918cf1
CE
1(* trace-cml.sml
2 *
3 * COPYRIGHT (c) 1992 AT&T Bell Laboratories
4 *
5 * This module provides rudimentary debugging support in the form of mechanisms
6 * to control debugging output, and to monitor thread termination. This
7 * version of this module is adapted from Cliff Krumvieda's utility for tracing
8 * CML programs. It provides three facilities: trace modules, for controlling
9 * debugging output; thread watching, for detecting thread termination; and
10 * a mechanism for reporting uncaught exceptions on a per thread basis.
11 *)
12
13structure TraceCML : TRACE_CML =
14 struct
15
16 structure SV = SyncVar
17
18 (* where to direct trace output to *)
19 datatype trace_to
20 = TraceToOut
21 | TraceToErr
22 | TraceToNull
23 | TraceToFile of string
24 | TraceToStream of TextIO.outstream
25
26 exception NoSuchModule
27
28 (** Trace Modules **)
29 datatype trace_module = TM of {
30 full_name : string,
31 label : string,
32 tracing : bool ref,
33 children : trace_module list ref
34 }
35
36 val traceRoot = TM{
37 full_name = "/",
38 label = "",
39 tracing = ref false,
40 children = ref []
41 }
42
43 fun forAll f = let
44 fun for (tm as TM{children, ...}) = (f tm; forChildren(!children))
45 and forChildren [] = ()
46 | forChildren (tm::r) = (for tm; forChildren r)
47 in
48 for
49 end
50
51 structure SS = Substring
52
53 fun findTraceModule name = let
54 fun eq ss (TM{label, ...}) = (SS.compare(SS.all label, ss) = EQUAL)
55 fun find ([], tm) = SOME tm
56 | find (arc::rest, tm as TM{label, children, ...}) = let
57 val eqArc = eq arc
58 fun findChild [] = NONE
59 | findChild (c::r) =
60 if (eqArc c) then find(rest, c) else findChild r
61 in
62 findChild (!children)
63 end
64 in
65 find (
66 SS.tokens (fn #"/" => true | _ => false) (SS.all name),
67 traceRoot)
68 end
69
70 fun traceModule' (TM parent, name) = let
71 fun checkChildren [] = let
72 val tm = TM{
73 full_name = (#full_name parent ^ name),
74 label = name,
75 tracing = ref(!(#tracing parent)),
76 children = ref []
77 }
78 in
79 (#children parent) := tm :: !(#children parent);
80 tm
81 end
82 | checkChildren((tm as TM{label, ...})::r) =
83 if (label = name) then tm else checkChildren r
84 in
85 checkChildren (! (#children parent))
86 end
87
88 (* return the name of the module *)
89 fun nameOf (TM{full_name, ...}) = full_name
90
91 (* return the module specified by the given string *)
92 fun moduleOf' name = (case findTraceModule name
93 of NONE => raise NoSuchModule
94 | (SOME tm) => tm
95 (* end case *))
96
97 (* turn tracing on for a module and its descendents *)
98 val traceOn' = forAll (fn (TM{tracing, ...}) => tracing := true)
99
100 (* turn tracing off for a module and its descendents *)
101 val traceOff' = forAll (fn (TM{tracing, ...}) => tracing := false)
102
103 (* turn tracing on for a module (but not for its descendents) *)
104 fun traceOnly' (TM{tracing, ...}) = tracing := true
105
106 (* return true if this module is being traced *)
107 fun amTracing (TM{tracing, ...}) = !tracing
108
109 (* return a list of the registered modules dominated by the given
110 * module, and their status.
111 *)
112 fun status' root = let
113 fun list (tm as TM{tracing, children, ...}, l) =
114 listChildren (!children, (tm, !tracing)::l)
115 and listChildren ([], l) = l
116 | listChildren (c::r, l) = listChildren(r, list(c, l))
117 in
118 rev (list (root, []))
119 end
120
121 (** Trace printing **)
122 val traceDst = ref TraceToOut
123 val traceCleanup = ref (fn () => ())
124
125 fun setTraceFile' t = traceDst := t
126
127(** NOTE: there are bookkeeping bugs, when changing the trace destination
128 ** from TraceToStream to something else (where the original destination
129 ** was TraceToFile).
130 **)
131 fun tracePrint s = let
132 fun output strm = (TextIO.output(strm, s); TextIO.flushOut strm)
133 in
134 case !traceDst
135 of TraceToOut => output TextIO.stdOut
136 | TraceToErr => output TextIO.stdErr
137 | TraceToNull => ()
138 | (TraceToFile fname) => let
139 val dst = let
140 val strm = TextIO.openOut fname
141 in
142 traceCleanup := (fn () => TextIO.closeOut strm);
143 TraceToStream strm
144 end handle _ => (
145 Debug.sayDebug(concat[
146 "TraceCML: unable to open \"", fname,
147 "\", redirecting to stdout"
148 ]);
149 TraceToOut)
150 in
151 setTraceFile' dst;
152 tracePrint s
153 end
154 | (TraceToStream strm) => output strm
155 (* end case *)
156 end
157
158 (** Trace server **)
159 val traceCh : (unit -> string list) CML.chan = CML.channel()
160 val traceUpdateCh : (unit -> unit) CML.chan = CML.channel()
161
162 fun traceServer () = let
163 val evt = [
164 CML.wrap(CML.recvEvt traceCh, fn f => tracePrint(concat(f()))),
165 CML.wrap(CML.recvEvt traceUpdateCh, fn f => f())
166 ]
167 fun loop () = (CML.select evt; loop())
168 in
169 loop()
170 end (* traceServer *)
171
172 fun tracerStart () = (CML.spawn traceServer; ())
173 fun tracerStop () = ((!traceCleanup)(); traceCleanup := (fn () => ()))
174
175 val _ = (
176 RunCML.logChannel ("TraceCML:trace", traceCh);
177 RunCML.logChannel ("TraceCML:trace-update", traceUpdateCh);
178 RunCML.logServer ("TraceCML:trace-server", tracerStart, tracerStop))
179
180 local
181 fun carefully f = if RunCML.isRunning()
182 then CML.send(traceUpdateCh, f)
183 else f()
184 fun carefully' f = if RunCML.isRunning()
185 then let
186 val reply = SV.iVar()
187 in
188 CML.send (traceUpdateCh, fn () => (SV.iPut(reply, f())));
189 SV.iGet reply
190 end
191 else f()
192 in
193 fun traceModule arg = carefully' (fn () => traceModule' arg)
194 fun moduleOf name = carefully' (fn () => moduleOf' name)
195 fun traceOn tm = carefully (fn () => traceOn' tm)
196 fun traceOff tm = carefully (fn () => traceOff' tm)
197 fun traceOnly tm = carefully (fn () => traceOnly' tm)
198 fun setTraceFile f = carefully (fn () => setTraceFile' f)
199 fun status root = carefully' (fn () => status' root)
200 end (* local *)
201
202 fun trace (TM{tracing, ...}, prFn) =
203 if (RunCML.isRunning() andalso (!tracing))
204 then CML.send(traceCh, prFn)
205 else ()
206
207
208 (** Thread watching **)
209
210 (* controls printing of thread watching messages *)
211 val watcher = traceModule (traceRoot, "ThreadWatcher")
212 val _ = traceOn watcher
213
214 datatype watcher_msg
215 = WATCH of (CML.thread_id * unit CML.chan)
216 | UNWATCH of (CML.thread_id * unit SV.ivar)
217
218 val watcherMb : watcher_msg Mailbox.mbox = Mailbox.mailbox ()
219
220 (* stop watching the named thread *)
221 fun unwatch tid = let
222 val ackV = SV.iVar()
223 in
224 Mailbox.send(watcherMb, UNWATCH(tid, ackV));
225 SV.iGet ackV
226 end
227
228 (* watch the given thread for unexpected termination *)
229 fun watch (name, tid) = let
230 val unwatchCh = CML.channel()
231 fun handleTermination () = (
232 trace (watcher, fn () => [
233 "WARNING! Watched thread ", name, CML.tidToString tid,
234 " has died.\n"
235 ]);
236 unwatch tid)
237 fun watcherThread () = (
238 Mailbox.send (watcherMb, WATCH(tid, unwatchCh));
239 CML.select [
240 CML.recvEvt unwatchCh,
241 CML.wrap (CML.joinEvt tid, handleTermination)
242 ])
243 in
244 CML.spawn (watcherThread); ()
245 end
246
247 structure TidTbl = HashTableFn (
248 struct
249 type hash_key = CML.thread_id
250 val hashVal = CML.hashTid
251 val sameKey = CML.sameTid
252 end)
253
254 (* the watcher server *)
255 fun startWatcher () = let
256 val tbl = TidTbl.mkTable (32, Fail "startWatcher")
257 fun loop () = (case (Mailbox.recv watcherMb)
258 of (WATCH arg) => TidTbl.insert tbl arg
259 | (UNWATCH(tid, ack)) => (
260 (* notify the watcher that the thread is no longer being
261 * watched, and then acknowledge the unwatch command.
262 *)
263 CML.send(TidTbl.remove tbl tid, ())
264 handle _ => ();
265 (* acknowledge that the thread has been removed *)
266 SV.iPut(ack, ()))
267 (* end case *);
268 loop ())
269 in
270 CML.spawn loop; ()
271 end
272
273 val _ = (
274 RunCML.logMailbox ("TraceCML:watcherMb", watcherMb);
275 RunCML.logServer ("TraceCML:watcher-server", startWatcher, fn () => ()))
276
277
278 (** Uncaught exception handling **)
279
280 fun defaultHandlerFn (tid, ex) = let
281 val raisedAt = (case (SMLofNJ.exnHistory ex)
282 of [] => ["\n"]
283 | l => [" raised at ", List.last l, "\n"]
284 (* end case *))
285 in
286 Debug.sayDebug (concat ([
287 CML.tidToString tid, " uncaught exception ",
288 exnName ex, " [", exnMessage ex, "]"
289 ] @ raisedAt))
290 end
291
292 val defaultHandler = ref defaultHandlerFn
293 val handlers = ref ([] : ((CML.thread_id * exn) -> bool) list)
294
295 (* this sets the default uncaught exception action. *)
296 fun setUncaughtFn' action = defaultHandler := action
297
298 (* add an additional uncaught exception action. If the action returns
299 * true, then no further action is taken. This can be used to handle
300 * handle application specific exceptions.
301 *)
302 fun setHandleFn' action = handlers := action :: !handlers
303
304 (* this resets the default uncaught exception action to the system default,
305 * and removes any layered actions.
306 *)
307 fun resetUncaughtFn' () = (defaultHandler := defaultHandlerFn; handlers := [])
308
309 val exnUpdateCh : (unit -> unit) CML.chan = CML.channel()
310
311 fun exnServerStartup () = let
312 val errCh = Mailbox.mailbox()
313 (* this function is installed as the default handler for threads;
314 * it sends the thread ID and uncaught exception to the ExnServer.
315 *)
316 fun threadHandler exn = Mailbox.send(errCh, (CML.getTid(), exn))
317 (* invoke the hsndler actions on the uncaught exception *)
318 fun handleExn arg = let
319 val hdlrList = !handlers and dfltHndlr = !defaultHandler
320 fun loop [] = dfltHndlr arg
321 | loop (hdlr::r) = if (hdlr arg) then () else loop r
322 in
323 CML.spawn (fn () => ((loop hdlrList) handle _ => (dfltHndlr arg)));
324 ()
325 end
326 val event = [
327 CML.wrap (CML.recvEvt exnUpdateCh, fn f => f()),
328 CML.wrap (Mailbox.recvEvt errCh, handleExn)
329 ]
330 fun server () = (CML.select event; server())
331 in
332 Thread.defaultExnHandler := threadHandler;
333 CML.spawn server; ()
334 end
335
336 val _ = (
337 RunCML.logChannel ("TraceCML:exnUpdateCh", exnUpdateCh);
338 RunCML.logServer ("TraceCML", exnServerStartup, fn () => ()))
339
340 local
341 fun carefully f = if RunCML.isRunning() then CML.send(exnUpdateCh, f) else f()
342 in
343 fun setUncaughtFn arg = carefully (fn () => setUncaughtFn' arg)
344 fun setHandleFn arg = carefully (fn () => setHandleFn' arg)
345 fun resetUncaughtFn arg = carefully (fn () => resetUncaughtFn' arg)
346 end (* local *)
347
348 end; (* TraceCML *)