Exception Handling with Resumption

Motivation

The basic idea of exception handling in modern programming languages is to separate the detection of an exceptional situation from the code that handles it. While control is being transferred from the point where an exception has been raised to the point where it is handled, the runtime stack is usually ``unwound,'' causing all expressions, statements, and function invocations in between to terminate abruptly. In particular, it is impossible to transfer control back to the raising point and resume execution there, even if the exception handler had been able to ``cure'' the cause of the problem.
Applying this ``termination behaviour'' to a real-life situation such as a tire breakdown would mean to abort the entire journey (corresponding to a possibly long and complex computation) and return to the starting point, instead of changing wheels there and then (executing an exception handler) and afterwards continuing the journey (resuming execution at the exception point).
To give a more programming-related example, if an out-of-memory error is raised during a complex computation and the responsible exception handler is able to regain some memory (e. g., by freeing data structures which merely cache frequently needed values for performance reasons), it would be highly desirable to resume the interrupted computation afterwards, just as if nothing has happened. In the C++ standard library, this is achieved by invoking a so-called new_handler function instead of immediately raising an exception; if this function is able to obtain additional memory, it returns normally and execution continues, while otherwise it raises an exception, causing the current computation to be aborted.

Even though the C++ approach of handling out-of-memory situations solves the problem at hand, it actually reveals a shortcoming of today's exception handling mechanisms, as it cannot be realized with exceptions alone, but requires an additional handler function. Furthermore, it is tailored to out-of-memory situations, even though resuming execution after having successfully handled an exception would make sense in other situations as well.

Obvious Solution

The most obvious solution to the problems described above is to extend today's exception handling mechanisms with the possibility of resumption. Syntactically, this can be achieved by allowing a throw statement to have one or more accept clauses, which are syntactically quite similar to the catch clauses of a try statement (cf. the example code below). If a throw statement possesses accept clauses, the runtime stack is not unwound while searching for a matching handler in the usual way. If the handler, i. e., catch block, is able to solve the problem that caused the exception, it can cause execution to be resumed at the throw statement by executing a resume statement. In the same way a throw statement is able to ``transport'' information to a catch block by passing an exception object, a resume statement is able to transport information back to the throw statement by passing a ``solution'' object to one of its accept blocks. If the handler does not execute a resume statement, because it has not been able to cure the problem, execution continues as usual after the try statement; however, since the runtime stack has not been unwound yet, this is done now before actually continuing.

    try {
        ......
        throw new SomeException(...)
        accept (Solution1 s1) { ...... }
        accept (Solution2 s2) { ...... }
        ......
    }
    catch (SomeException x) {
        ......
        if (/* problem has been solved someway */) {
            resume new Solution1(...);
        }
        else if (/* problem has been solved another way */) {
            resume new Solution2(...);
        }
    }
If a throw statement does not possess accept clauses, it is executed in the usual way, i. e., the stack is unwound while searching for a matching handler, implying that resumption is impossible in that case. If the catch block executes a resume statement anyway - which cannot be statically prevented by the compiler, since throw statements and catch clauses may reside in different methods (of different classes) and the same catch block could handle both exceptions thrown by simple, non-resumable throw statements and by resumable throw statements with accept clauses -, this could be either simply ignored or throw itself a (non-resumable) runtime exception. The same is true if a resume statement cannot find a matching accept clause.

Alternative Solution

A completely different solution to the problems described in the motivation section, is to entirely replace existing exception handling mechanisms with a totally different approach. This is in fact possible using global and local virtual functions and non-local jump statements. The interested reader is referred to the respective publication mentioned below.

Publications

[1] A. Gruler, C. Heinlein: "Exception Handling with Resumption: Design and Implementation in Java." In: H. R. Arabnia (ed.): Proc. Int. Conf. on Programming Languages and Compilers (PLC'05) (Las Vegas, NV, June 2005), 165-171. (PostScript, PDF)
Describes the ``obvious solution'' mentioned above, i. e., an extension of the Java exception handling mechanism with resume statements and accept clauses.

[2] C. Heinlein: "Local Virtual Functions." In: R. Hirschfeld, R. Kowalczyk, A. Polze, M. Weske (eds.): NODe 2005, GSEM 2005 (Erfurt, Germany, September 2005). Lecture Notes in Informatics P-69, Gesellschaft für Informatik e. V., Bonn, 2005, 129-144. (PostScript, PDF)
Describes local virtual functions as an extension of global virtual functions as well as non-local jump statements, which can be used, amongst others, to achieve exception handling with the possibility of resumption, without requiring an explicit exception handling mechanism at all.


Christian Heinlein, 22.09.09