001 ZOOM Exception Mechanism Design
002 -------------------------------
003
004 The Zoom exception mechanism is intended to be the tools used by ZOOM modules
005 to handle exceptions in a useful way and provide the user with interfaces to
006 control how to react to problems. It is not (at this time) intended to be a
007 mechanism for the user to hook into to define his own exceptions, although that
008 may come later. Frankly, successful implementation and use by ZOOM modules will
009 be necessary to sell this structure to our users as potentially useful, anyway.
010
011 We need the mechanism "now" because we are trying to deliver at least two
012 packages which will have to be re-instrumented when the mechanism is finally
013 adopted.
014
015 This is not a "signal handler" mechanism, which could be used to establish
016 behavior for segment violations, arthmetic exceptions, and so forth. It
017 would be nice to have this but C++ does not provide generic support for
018 those activities. A **later** possibility will be to provide this support for
019 POSIX-compliant operating systems, and coordinate it with the exception
020 mechanism.
021
022
023 We need to be able to cope with the following realities:
024 --------------------------------------------------------
025
026 1 - Users will not, in every case, imbed calls into a try block.
027
028 2 - Frameworks will incorporate code from multiple users and in some
029 circumstances cannot afford to abort the entire job if some rare path
030 in one user's code blows up. To the extent we can help with this, we must.
031
032 3 - Framework creators CAN be expected to imbed portions in try blocks, or
033 set up behaviors for various exceptions, assuming we give them the necessary
034 tools.
035
036 In the remainder of this document, the "user" who sets things up is assmued
037 to be such a framework manager.
038
039 4 - There is need for coordinated logging and related behavior.
040
041
042 To be able to satisfy this, the goal is to allow:
043 -------------------------------------------------
044
045 1 - The user should be able to specify, for a given sort of exception, whether
046 she wants to:
047
048 a - Throw the exception via the C++ mechanism, thus aborting unless she
049 in the framework or a lower-level user responsible catchs the problem.
050
051 b - Invoke a user-written handler when the exception occurs, which may
052 itself determine it is necessary to throw the exception.
053
054 c - Ignore the exception RETURNING TO THE SPOT IN THE ZOOM MODULE
055 THAT DETECTED THE PROBLEM. This can happen with or without a handler
056 being invoked. Typically, the module will then return some approriate
057 pseudo-value to the user.
058
059 2 - In cases where exceptions are to be handled or ignored, there should be
060 a well-known way to get information about the existance of a problem,
061 analogous to the errno mechanism in C.
062
063 3 - Explanatory strings should be associated with the problem at the point
064 of origin.
065
066 4 - The exceptions are organized in a hierarchical manner, which uniformly
067 applies one simple category philosophy that the users can understand,
068 across the various ZOOM packages.
069
070
071 With this in mind, our mechanism has the following structure:
072 -------------------------------------------------------------
073
074
075 1 - Upon detecting a problem for which it would like to (potential) throw an
076 exception, the module code will instead invoke the ZMthrow macro.
077
078 2 - ZMthrow takes as its argument a ZMexception object constructor, which
079 has as ITS first argument a const char*. The intent is for the ZMexception
080 object actually do be derived from the base ZMexception class, and for
081 the char* first argument to contain an explanatory message. Particular
082 ZMexceptions can also have construction forms taking a second string, or
083 whatever.
084 For example,
085 ZMthrow ( HepTuple::ZMxCapture( "Column not found", name ) );
086
087 3 - The ZMthrow macro appends __LINE__, __FILE__ and calls ZMexcept.
088
089 4 - ZMexcept has signature
090
091 void ZMexcept ( ZMexception x, int line,
092 char file[], char data[], char time[]);
093
094 It does the following:
095
096 a - Places x.ZMexceptionId into a circular buffer ZMerrno. Actually,
097 constructing the ZMexcept object does that. More about ZMerrno later.
098
099 b - Determines for this exception what sort of logging is enabled, and logs
100 it. Note that derived exceptions must modify the general logging
101 method if they wish to include information beyond the message and
102 file/line/time stamp.
103
104 c - Determines whether a handler has been established, and if so invokes it.
105 The handler will be passed the exception object, and can be set up to
106 also take the line/file/time arguments. The handler returns a bool
107 which if true will say to throw the exception.
108
109 d - Determines (after any handler has been invoked) whether to ignore the
110 exception. It will do either
111 throw x;
112 or
113 return;
114
115 5 - ZMerrno is analogous to the C/Unix errno mechanism, but allows viewing a
116 history of the last N problems detected. We anticipate using it like errno,
117 via exception id, but we copies of place the whole exception object onto
118 ZMerrno to provide more info if desired. The interface is:
119
120 a - ((creation somehow, specifying N))
121
122 b - ZMerrno.write (ZMexcption* x); // copy an exception onto ZMerrno
123
124 The user would not use a and b but would use:
125
126 c - int ZMerrno.read (); // read the last id value on ZMerrno
127 int ZMerrno.read (int k); // read the last-but-k id value on ZMerrno
128
129 d - void ZMerrno.clear(); // put a zero (indicating no current error)
130 // on ZMerrno.
131
132 e - void ZMerrno.pop(); // remove an entry setting the top to the
133 // previous entry. For instance, if you
134 // have a loop in which some known ignorable
135 // happening places a value on ZMerrno, you
136 // can pop each one so as not to wipe out
137 // the history for others.
138
139 f - ZMexception* ZMerrno.get() // Return pointer to the last or
140 ZMexception* ZMerrno.get(int k) // last-but-k exception on ZMerrno.
141 // Allows perusal of things like the
142 // message and the logger and handler
143 // used when the exception was
144 // encountered.
145
146 Thus after the start, or after ZMerrno.clear(), the user can always find
147 out whether any ignored exceptions had occured by doing
148 if (ZMerrno.read()) { ... then something has happened }
149
150 If we later incorporate signal handling, this ZMerrno stack is where the
151 user can find out whether he has had his arithmetic error since the last
152 clear.
153
154 ZMerrno is pronounced "oops."
155
156 6 - The id 0 indicates no error. Each upper 16-bit value is assigned to a
157 module to avoid conflicts; the values starting with upper bit set are
158 reserved for user definition at a later date. The lower 16 bits distinguish
159 the specific error.
160
161 Each ZMexception object class has its own error id which is established
162 (hardwired) at construction.
163
164 The ZMexception codes for the upper 16 bits are defined in ZMexception.h.
165 The lower 16 bits, for each module are defined in files with names like
166 HepTupleZMexception.h.
167
168 7 - When a ZMexcept exception is thrown it does a ZMerrno.write(this.id). Thus
169 ZMerrno tracks these exceptions even if they are explicitly thrown and
170 caught by the user outside the ZMthrow/ZMexception mechanism.
171
172 8 - Logging is discussed separately.
173
174
175 The user interface to control exception behavior is:
176 -----------------------------------------------------
177
178 By the way, this is an interface for the framework manager to use; the lower
179 level user would generally never establish handlers or ignores, and would
180 only use the ZMerrno.read() and .clear().
181
182 1 - To establish a handler for a particular exception type, say for
183 ZMexceptCapture:
184
185 HepTuple::ZMxCapture.setHandler ( myhandler, "handlerName" );
186
187 The handlerName string gives a convenient way to tell about the
188 handling in log messages ("... was handled by mySuperHandler").
189
190 The signature of the handler must be:
191 bool myhandler ( ZMexception x );
192 We will have a ZMhandler class to contain that and the handlerName.
193
194 Note that the handler has access to the fields of x including for all
195 ZMexception objects:
196 char* message;
197 int line;
198 char* sourceFileName;
199 int serialNumber;
200 ZMhandler handler;
201 ZMlogger logger;
202 // and class-wide data:
203 static int id;
204 static char* messagePreamble;
205 static int count;
206 and, for particular derived ZMexception objects, any secondary messages
207 or other information provided to the constructor and kept in the object.
208
209 The handler should return false to ignore the exception and return to
210 the user code (from ZMexcept), or true to throw the exception after
211 the handler returns. The user can also throw an exception explicitly
212 in the handler.
213
214 2 - You may establish a handler for an entire base class, which applies to any
215 ZMexceptions derived from in that class for which no specific handler is
216 established. For example, HepTuple::ZMxNewColumn is derived
217 from HepTuple::ZMxGeneral so
218
219 HepTuple::ZMxGeneral.setHandler ( myDefaultHandler );
220
221 will apply to HepTuple::ZMxNewColumn if that is ever ZMthrown.
222
223 3 - Note that if a handler is established for a subclass it takes precedence
224 over that for the base class. If you instead wish to call the base class
225 handler after executing the specific handler, you must invoke it explicitly.
226 Only if no handler has been established will the exception check for and
227 invoke a handler in its base class automatically.
228
229 3a- At times you may wish to prevent the calling of the base class handler yet
230 have no need for explicit handling. The mechanism provides ZMtrivialHandler
231 which merely checks whether (in the absense of a handler) the exception
232 would be ignored or thrown, and returns false or true accordingly.
233
234 Conversely, setting the handler to NULL will cause it to revert to the
235 initial behavior of defering to the handler of the parent class.
236
237 4 - The user can tell the system to whether or not to ignore an unhandled
238 ZMexception:
239
240 HepTuple::ZMxCapture.ignore()
241 HepTuple::ZMxCapture.dontIgnore()
242
243 The default is don't ignore -- meaning throw the exception unless a handler
244 is invoked and comes back false. The same rules for inheritance apply as
245 in the handler case: If you haven't specifically said anything about a
246 subclass, then what you have said about the parent class will apply. Thus
247 calling dontIgnore() is NOT the same as not calling ignore(). In analogy
248 with handling, we need a way to say "pretend I never said ignore or dont;
249 use the parent class decision". This is:
250
251 HepTuple::ZMxCapture.useParentIgnore()
252
253 5 - Sometimes you may want to ignore an exception the first N times and then
254 react differently. The handler can have a static count variable to
255 implement this behavior. Alternatively, there is a call to establish this
256 behavior even if no handler is used:
257 HepTuple::ZMxCapture.ignore(N)
258
259
260
261
262 The part of the interface seen by non-framework-manager users is simpler:
263 --------------------------------------------------------------------------
264
265 1 - The ZMerrno methods may be used to see if (ignored) exceptions have
266 happened. Typically, the user used to Unix exceptions would call
267 ZNerrno.read() and maybe ZMerrno.clear().
268
269 2 - The ordinary user can try, and catch, these ZMexceptions. We ask that
270 usrs not throw ZMexceptions that the ZOOM modules throw. Later we may
271 extend support to include user-defined subclasses of ZMexception.
272
273 3 - When an exception occurs and is not ignored, the user will know the
274 message given for the exception, as well as the and line number where the
275 ZMthrow macro was invoked. This is inside ZOOM code. By doing
276 debug core <usersource> the user can get a traceback into the user
277 routines to find which line of his code encountered the problem.
278
279 4 - The status of what was set up for ignoring and handling may be probed, so
280 temporary control behavior can be set and then put back to what it was:
281
282 handler() returns the established handler.
283 logger() returns the established logger.
284
285 int ignoreStatus(); // -2 - ignore was specified (or ignore N was
286 // specified but have none left)
287 // -1 - dontIgnore was specified
288 // positive integer - ignore N times was specified
289 // and have this number left
290 // 0 - neither was specified; use policy from
291 // parent
292
293 void setIgnorePolicy(int);
294
295
296 Logging is handled as follows:
297 ------------------------------
298
299 0 - The connection between a class of ZMexceptions and logging that is through a
300 ZMlogger. The (framework) user may create her own logger but typically will
301 use our provided class ZMlog, which has is a ZMlogger and has a constructor
302 taking a file name. Any actions taken by the logger we describe below
303 will refer to how ZMlog behaves.
304
305 1 - Every individual ZMexception object can have a method for creating
306 (from the other arguments aside from messge) a string to put into a log.
307 (Of course, it may be inherited from its base class.) But the message
308 as well as time, line, id, and so forth is not to be handled by each;
309 instead, ZMthrow handles this.
310
311 The method to create the string is logMessage().
312
313 ZMexcept will at various points call the logThis() method of the established
314 logger when it wants to log information.
315
316 1a- The user assgns a ZMlogger to an exception class by
317 ZMexception::setLogger(ZMlogger*).
318 For example,
319
320 ZMlog* mylog ("logfilename.txt");
321 ZMxCapture::setLogger(mylog);
322
323 The pointer returned should be checked. In the case of ZMlog, it can be
324 NULL for two reasons: The file cannot be opened for append, or the file
325 is already open for some other purpose. (If it is already opened for
326 logging, that is fine; this logger will also cause logging of messages
327 there).
328
329 2 - ZMexception has a method setLogger(ZMlogger) which will open a log using
330 that logger for that type of exception.
331
332 This will establish this logger to be used for exceptions of this class.
333 In the case of ZMlog, that means it will establish the file which was
334 provided when the logger was constructed, as a logging point for exceptions
335 of this class. (This is class static information.)
336
337 2a- The ZMlog object will log to a file: its constructor will open a log to
338 that file for that type of exception.
339
340 A user can provide a different logger which, for example, ties into the
341 CDF or D0 general logging mechanism instead of writing to a file.
342
343 3 - If no logging is specified for a class the file defaults to the base class.
344 Thus HepTuple::ZMxCapture might log to the file for HepTuple::ZMxGeneral,
345 or if no logging is established there, for ZMexception. It is possible (the
346 default) that no logging is established anywhere.
347
348 4 - You may log to multiple files; each ZMexception (sub)class has a class
349 static linked list of log files.
350
351
352 - - - - up to here
353
354
355
356
357 5 - Aside from single files, you can also establish a "rolling log" pair of
358 files:
359 bool ZMexception::log(const char filename1[],
360 int n, const char filename2[], );
361 The way this works is that the first file is opened (still for append),
362 and up to n exception instances are logged into it, at which point
363 it is closed, renamed to the second file, and re-created (empty) for
364 more logging. A script can detect when this has happended and archive
365 the second file if desired.
366
367 6 - If you wish to cease logging a particular exception type in general or
368 to a particular log file, you may:
369 HepTuple::ZMxCapture.stopLog();
370 HepTuple::ZMxCapture.stopLog(filename);
371 If no exceptions are logging to a particular log anymore, that file will
372 be closed.
373
374 7 - To be able to temporarily modify logging behavior for an exception, you
375 may call
376 int saveLogInstructions(),
377 and later call
378 restoreLogInstructions(int).
379
380
381
382 The following usage recommendations pertain to writers of ZOOM code:
383 --------------------------------------------------------------------
384
385 As shown in the examples, place your definitions of the exception objects
386 inside the class definition for the class. That way, methods within this
387 class can simply call the shorter name -- ZMxCapture rather than
388 ZMxHepTupleCapture.
389
390 Don't create too many types of exceptions. The error codes appearing on the
391 ZMerrno stack are defined per each type, but unless you envision the user
392 needing to AUTOMATICALLY distinguish between one problem and another in
393 the same submodule via ZMerrno, don't define them as two separate exception
394 types.
395
396 In cases where the user interface promises a routine will not throw an
397 exception (but might return false if something goes awry) the ZOOM code
398 itself should enclose any possible ZMthrows in try blocks, to catch the problem
399 in case the user has not specified ignoring the exception.
400
401 Note that the argument to the constructor of the ZMexception in ZMthrow can
402 (and often will) be a string formed by concatenating some fixed informatory
403 message with some variable information, as in
404 ZMthrow ( ZMxCapture( "Column not found", name ) );
405 To do this, one should have a constructor for that sort of ZMexceptoin object
406 with the extra argument in its signature, as well as the one with just a
407 const char[]. One could alternatively do someting like
408 ZMthrow ( ZMxCapture( strcat("Column not found", name) ) );
409 but if you use the exception in more than one place, the recommended second
410 constructor is less work.
411
412 When returning a pointer, in circumstances where things have gone wrong, the
413 usual action is to return a null pointer. In many cases this is appropriate,
414 especially if the user interface defines it. However, the sloppy user might
415 cause an (uncatchable) bus error if he does not check for null pointer.
416 Consider returning a pointer to a braindead object (whose methods throw
417 ignorable ZMthrows) rather than NULL -- then the job might not abort.
418
419
420 Documentation should be layered:
421
422 1 - How the user interacts with the mechanism (VERY brief).
423
424 2 - How the framework manager user interacts (settting up handlers, logs,
425 ignores).
426
427 3 - How to define a ZMx class.
428
429 4 - Usage recommendations for writers of ZOOM code.
430
Due to the LXR bug, the updates fail sometimes to remove references to deleted files. The Saturday's full rebuilds fix these problems |
This page was automatically generated by the
LXR engine.
|
|