mailRe: RelaxWarnings and RelaxErrors.


Others Months | Index by Date | Thread Index
>>   [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Header


Content

Posted by Edward d'Auvergne on August 15, 2006 - 07:58:
> I'm not so keen about the 'try:' system.  For small programs it works
> beautifully.  However within relax exceptions can be quite difficult
> to handle.  For example handling an exception thrown by thread (in
> threading mode this is a major headache that I haven't fully solved).
> This will come up again if MPI is implemented.  Another issue is
> exception handling when there is a mixture of Python and C.  It would
> be useful if we could precisely define this behaviour.  A grep for try
> statement should show how much of a hack the try system is within
> relax.  If there are any useful properties of exceptions that are lost
> though - then it may not be worth dropping the system (unless we can
> mimic anything that seems useful).
>

I know very little about threading, and only slightly more about MPI, so
I don't really understand the basis of the problems, but it seems to me
that whatever happens with relax errors, there will always be builtin
Python exceptions that need to be dealt with. As such these are problems
that need to be resolved whatever is decided about relax errors. Given
that, the benefits of Python's exception handling are worth keeping
because we are stuck with their disadvantages.

The only problem with exceptions is that they break inner loops and terminate all functions nested below the try statement so that continued execution from within the loop or nested functions is not possible - it must be restarted. This can be both good and bad. With threads it means a lot of re-spawning of threads if not dealt with - however the threading system also does weird things with the exception system.

> > This means that while its easy to
> > generate classes of error with specific and complex behaviour, its not
> > easy to override that behaviour in a specific context.
>
> The __call__() function can be defined specifically at runtime within
> the 'error.py' file.  But if necessary any part of relax can override
> it with a new function (although that should probably not be done).
>

This isn't quite what I mean. Consider some function in relax that is
called in many different contexts. Imagine that it is prone to a
particular type of error, which we signal by a RelaxError. Now it is
quite possible that the correct response to that error depends on the
context in which we are calling this function. eg. in some cases the
error might signal a fatal error in the data that the user must fix,
while in other contexts it might be something simple that can be fixed
silently and re-tried. If the error is an exception, in the first
context, the code which calls the function will simply do so without
catching the error so it is propagated to the user. In the second case,
the code will call the function in a try statement, with the fix in the
except clause. On the other hand, if relax errors are just ordinary
functions that are called when an error arises, there is no easy way of
dealing with these two cases - the error has the same effect independent
of the context from which it is called. It is this lack of flexibility
that concerns me when we talk about not using exceptions for our error
handling.

This could be rectified by a flag, so the following examples will have very similar functionality to the try-raise-except paradigm but without the breaking of inner loops and functions:

RelaxBadError('Hello', exit=1)   # Print the error to stderr and quit.
RelaxBadError('Hello', exit=0)   # Print the error to stderr but
continue operating as if nothing happened.

If this is too low level then the self.exception() function can be
re-pointed to either self.error() or self.error_no_exit() as
necessary.  In the current setup these are set dependent on the UI
mode but can very easily be changed by other parts of relax.

In this thread I am challenging the entire try-raise-except paradigm.
I only setup this RelaxError system based on the Exception class
because chapter 8 of the Python tutorial says that this is what must
be done!  I am searching for the reason why?  Why is this the only
way?  Why should I blindly believe the Python gospel?

I don't believe this system is ideal, and it isn't perfect.  It is
completely Python centric.  In other programming languages you would
code the error system as functions (possibly within classes).  And
there are only two real behaviours I see as necessary for an error
system:  printing a message to stderr and terminating execution.
Possibly returning an error code to the shell would be useful as well.
Anything approaching this type of usability must be better than
messages such as 'FORTRAN STOP' or 'Segmentation fault'.  Therefore
why should I believe that this Python system is the way?  The more I
look into the details of this system and the more I think about how
'the infidels' (non-Python users ;) handle errors, the more sceptical
I'm becoming.

> > This issue that needs to be dealt with, then, is how to have the sort of
> > error handling at the users end that we want (errors and warnings that
> > can be up- and down-graded, rich debugging behaviour, etc) as well as
> > having good exception handling at the code level, for both builtin
> > Python exceptions and specific relax exceptions.
>
> We could leave the Python exceptions as is (the try statements usually
> end in 'expect: raise' so that these are always raised) while having
> relax defined errors which aren't actually exceptions.  It would be
> possible to define all of the necessary behaviour of Exception objects
> within the RelaxError objects while avoiding 'raise' statements.

I'm not sure how this could be achieved. The defining feature of an
exception is that it stops the normal program flow and starts looking
for an appropriate handling mechanism (ie. an appropriate except
statement) by propagating up the stack of function calls, examining each
calling function in turn for the applicable handler. Calling a function,
by definition, can only extend the stack downwards, it cannot regress up
the stack in the way an exception does.

I think that the raise statement generates a traceback object which keeps a copy of the stack at the position of the raise statement - as well as a few other details. However if the RelaxBadError, etc are called the current stack at that position is the sum of that usually held in the traceback and all the downstream functions. The separation is where the forth element of the stack 'stack[i][3]' matches RelaxBadError, etc. This is an easy trimming of the stack. During normal script execution I would like to further trim the stack. When an error occurs in a user function the traceback includes all the functions occurring within the prompt interface. These really don't need to be printed if the error occurs further downstream. This type of trimming isn't possible within the exception system.

Adding a few more elements to the stack as RelaxBadError does isn't a
problem as these can be ignored (I'm sure Exception does the same but
just uses the stack stored in the traceback object).  The only
difference is that the handling mechanism with the Exception objects
is above in the stack whereas in new system the handling mechanism is
lower down in the stack.  This, importantly, allows continuous
operation if desired.

> Another reason is that a RelaxError based on the Exception class is
> difficult when scripting.  The try statement will catch it and ignore
> it, but the execution of the script cannot be continued.  Then again,
> is there a situation where ignoring errors and continuing program
> execution would be useful?

I dont follow this. Try/except statements will only catch the exceptions
you tell them to catch, all others will be propagated. Clearly it is a
bad idea to cast except statements too broadly, but its an easy problem
to fix in the code.

The raise statement breaks the inner loops and function calls. It's just not possible to continue the script execution. This is because within 'prompt/interpreter.py' the 'try' function attempts to run the 'execfile()' function which executes the script. A raise statement within relax kills the execfile() function. If the raise statement occurs 1/3 of the way through the script - it's not possible to catch this and then continue with the other 2/3's because we have moved above the execfile() position of the stack. The try-raise-except paradigm is a very strong breaking system where you can only partially mitigate the damage using the try statement. Smooth continuous execution is just not possible.

> > In addition to the options we've already considered, there is the
> > possibility of coding appropriately rich behaviour into a replacement
> > function for sys.excepthook. This is the function that is called when an
> > exception is not caught by an appropriate handler. It's default
> > behaviour is to print the traceback and display the error. It is passed
> > both the exception class and the specific instance that was raised, as
> > well as the traceback object. This should be enough to get some quite
> > rich behaviour without too much hacking, I think.
>
> I wonder, what is more important - the 'raise' semantics, the
> Exception objects, and all the related behaviour or simply the
> printing out to stderr?  All the ancillary relax specific behaviour
> can be implemented in both systems.  But what are we trying to really
> achieve with the RelaxErrors?
>

Again I make the distinction between user level errors and exceptions.
From my point of view the important thing that errors must achieve for
the user is clear communication of the problem and useful debugging
features like saving state. Clearly Exception objects cannot achieve all
of this on their own, but I think we can code Exception handling
features into relax which do achieve these things. Downgrading an error
to a warning is more difficult to achieve, but is not necessary if
warnings are used where appropriate. I can only imagine wanting to
downgrade an error if I didn't think it should have been an error in the
first place, so better to raise a warning at that point.

Both systems can be modified to suit. I would never recommend downgrading an error when using the prompt or scripting but it may be useful within some other interface, for example a GUI.

Exceptions must achieve something very different inside relax - they
need to communicate unexpected results up the stack of function calls to
the point where they can be dealt with. Exceptions only become errors
when there is no way of dealing with them. As far as I am aware, the
only reasonable way of achieving this is through the Python Exception
propagation mechanism.

I think this has been successfully accomplished with the probationary RelaxErrorSystem class. The relevant part of the stack is printed to stderr, the error message is printed to stderr, and the program terminates. However when debugging is turned on, the state is also saved. I've also put into place the warning system which doesn't print the stack, prints the warning to stderr, and the program continues execution. I've also added two functions - one upgrades the warnings so that sys.exit(1) is called, the other downgrades the errors so that sys.exit(1) is not called. The interesting property of this new system is that smooth continuous operation is guaranteed. This is because it moves down the stack (which can be reversed) rather than up as the raise statement mandates (which cannot be reversed). The more interesting property from my point of view is that the warning and error systems are very similar. The Python Exception and Warning systems are incompatible. Therefore it makes it very easy to terminate the program using a flag such as '--pedantic' to enable the 'warning_pedantic' function.

The only two features of the Python Exception system that I can see
are the printing of the stack to stderr and the printing of the error
message to stderr.  Prevention of an error can be done within both
systems.

Edward



Related Messages


Powered by MHonArc, Updated Tue Aug 15 12:40:34 2006