Commit | Line | Data |
---|---|---|
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 | ||
13 | structure 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 *) |