source: doc/theses/andrew_beach_MMath/features.tex @ 9cdfa5fb

Last change on this file since 9cdfa5fb was 9cdfa5fb, checked in by Andrew Beach <ajbeach@…>, 3 years ago

Andrew MMath: Used (most of) Gregor's feedback to update the thesis. There are still a few \todo items as well as a general request for examples.

  • Property mode set to 100644
File size: 36.9 KB
1\chapter{Exception Features}
4This chapter covers the design and user interface of the \CFA EHM
5and begins with a general overview of EHMs. It is not a strict
6definition of all EHMs nor an exhaustive list of all possible features.
7However, it does cover the most common structure and features found in them.
9\section{Overview of EHMs}
10% We should cover what is an exception handling mechanism and what is an
11% exception before this. Probably in the introduction. Some of this could
12% move there.
13\subsection{Raise / Handle}
14An exception operation has two main parts: raise and handle.
15These terms are sometimes known as throw and catch but this work uses
16throw/catch as a particular kind of raise/handle.
17These are the two parts that the user writes and may
18be the only two pieces of the EHM that have any syntax in a language.
21The raise is the starting point for exception handling,
22by raising an exception, which passes it to
23the EHM.
25Some well known examples include the @throw@ statements of \Cpp and Java and
26the \code{Python}{raise} statement of Python. In real systems, a raise may
27perform some other work (such as memory management) but for the
28purposes of this overview that can be ignored.
31The primary purpose of an EHM is to run some user code to handle a raised
32exception. This code is given, along with some other information,
33in a handler.
35A handler has three common features: the previously mentioned user code, a
36region of code it guards and an exception label/condition that matches
37against the raised exception.
38Only raises inside the guarded region and raising exceptions that match the
39label can be handled by a given handler.
40If multiple handlers could can handle an exception,
41EHMs define a rule to pick one, such as ``best match" or ``first found".
43The @try@ statements of \Cpp, Java and Python are common examples. All three
44also show another common feature of handlers: they are grouped by the guarded
48After an exception is raised comes what is usually the biggest step for the
49EHM: finding and setting up the handler for execution.
50The propagation from raise to
51handler can be broken up into three different tasks: searching for a handler,
52matching against the handler and installing the handler.
55The EHM begins by searching for handlers that might be used to handle
56the exception.
57The search will find handlers that have the raise site in their guarded
59The search includes handlers in the current function, as well as any in
60callers on the stack that have the function call in their guarded region.
63Each handler found is with the raised exception. The exception
64label defines a condition that is used with the exception and decides if
65there is a match or not.
67In languages where the first match is used, this step is intertwined with
68searching; a match check is performed immediately after the search finds
69a handler.
72After a handler is chosen, it must be made ready to run.
73The implementation can vary widely to fit with the rest of the
74design of the EHM. The installation step might be trivial or it could be
75the most expensive step in handling an exception. The latter tends to be the
76case when stack unwinding is involved.
78If a matching handler is not guaranteed to be found, the EHM needs a
79different course of action for this case.
80This situation only occurs with unchecked exceptions as checked exceptions
81(such as in Java) can make the guarantee.
82The unhandled action is usually very general, such as aborting the program.
85A common way to organize exceptions is in a hierarchical structure.
86This pattern comes from object-orientated languages where the
87exception hierarchy is a natural extension of the object hierarchy.
89Consider the following exception hierarchy:
93A handler labeled with any given exception can handle exceptions of that
94type or any child type of that exception. The root of the exception hierarchy
95(here \code{C}{exception}) acts as a catch-all, leaf types catch single types
96and the exceptions in the middle can be used to catch different groups of
97related exceptions.
99This system has some notable advantages, such as multiple levels of grouping,
100the ability for libraries to add new exception types and the isolation
101between different sub-hierarchies.
102This design is used in \CFA even though it is not a object-orientated
103language, so different tools are used to create the hierarchy.
105% Could I cite the rational for the Python IO exception rework?
108After the handler has finished, the entire exception operation has to complete
109and continue executing somewhere else. This step is usually simple,
110both logically and in its implementation, as the installation of the handler
111is usually set up to do most of the work.
113The EHM can return control to many different places, where
114the most common are after the handler definition (termination)
115and after the raise (resumption).
118For effective exception handling, additional information is often passed
119from the raise to the handler and back again.
120So far, only communication of the exception's identity is covered.
121A common communication method for adding information to an exception
122is putting fields into the exception instance
123and giving the handler access to them.
124% You can either have pointers/references in the exception, or have p/rs to
125% the exception when it doesn't have to be copied.
126Passing references or pointers allows data at the raise location to be
127updated, passing information in both directions.
131%\todo{Maybe explain what "virtual" actually means.}
132Virtual types and casts are not part of \CFA's EHM nor are they required for
133an EHM.
134However, one of the best ways to support an exception hierarchy
135is via a virtual hierarchy and dispatch system.
136Ideally, the virtual system would have been part of \CFA before the work
137on exception handling began, but unfortunately it was not.
138Hence, only the features and framework needed for the EHM were
139designed and implemented for this thesis.
140Other features were considered to ensure that
141the structure could accommodate other desirable features in the future
142but are not implemented.
143The rest of this section only discusses the implemented subset of the
144virtual system design.
146The virtual system supports multiple ``trees" of types. Each tree is
147a simple hierarchy with a single root type. Each type in a tree has exactly
148one parent -- except for the root type which has zero parents -- and any
149number of children.
150Any type that belongs to any of these trees is called a virtual type.
151% A type's ancestors are its parent and its parent's ancestors.
152% The root type has no ancestors.
153% A type's descendants are its children and its children's descendants.
155For the purposes of illustration, a proposed, but unimplemented, syntax
156will be used. Each virtual type is represented by a trait with an annotation
157that makes it a virtual type. This annotation is empty for a root type, which
158creates a new tree:
160trait root_type(T) virtual() {}
162The annotation may also refer to any existing virtual type to make this new
163type a child of that type and part of the same tree. The parent may itself
164be a child or a root type and may have any number of existing children.
166% OK, for some reason the b and t positioning options are reversed here.
169trait child_a(T) virtual(root_type) {}
170trait grandchild(T) virtual(child_a) {}
171trait child_b(T) virtual(root_type) {}
180Every virtual type also has a list of virtual members and a unique id.
181Both are stored in a virtual table.
182Every instance of a virtual type also has a pointer to a virtual table stored
183in it, although there is no per-type virtual table as in many other languages.
185The list of virtual members is accumulated from the root type down the tree.
186Every virtual type
187inherits the list of virtual members from its parent and may add more
188virtual members to the end of the list which are passed on to its children.
189Again, using the unimplemented syntax this might look like:
191trait root_type(T) virtual() {
192        const char * to_string(T const & this);
193        unsigned int size;
196trait child_type(T) virtual(root_type) {
197        char * irrelevant_function(int, char);
200% Consider adding a diagram, but we might be good with the explanation.
202As @child_type@ is a child of @root_type@, it has the virtual members of
203@root_type@ (@to_string@ and @size@) as well as the one it declared
206It is important to note that these are virtual members, and may contain   
207arbitrary fields, functions or otherwise.
208The names ``size" and ``align" are reserved for the size and alignment of the
209virtual type, and are always automatically initialized as such.
210The other special case is uses of the trait's polymorphic argument
211(@T@ in the example), which are always updated to refer to the current
212virtual type. This allows functions that refer to the polymorphic argument
213to act as traditional virtual methods (@to_string@ in the example), as the
214object can always be passed to a virtual method in its virtual table.
216Up until this point, the virtual system is similar to ones found in
217object-oriented languages, but this is where \CFA diverges.
218Objects encapsulate a single set of methods in each type,
219universally across the entire program,
220and indeed all programs that use that type definition.
221The only way to change any method is to inherit and define a new type with
222its own universal implementation. In this sense,
223these object-oriented types are ``closed" and cannot be altered.
224% Because really they are class oriented.
226In \CFA, types do not encapsulate any code.
227Whether or not a type satisfies any given assertion, and hence any trait, is
228context sensitive. Types can begin to satisfy a trait, stop satisfying it or
229satisfy the same trait at any lexical location in the program.
230In this sense, a type's implementation in the set of functions and variables
231that allow it to satisfy a trait is ``open" and can change
232throughout the program.
233This capability means it is impossible to pick a single set of functions
234that represent a type's implementation across a program.
236\CFA side-steps this issue by not having a single virtual table for each
237type. A user can define virtual tables that are filled in at their
238declaration and given a name. Anywhere that name is visible, even if it is
239defined locally inside a function (although in this case the user must ensure
240it outlives any objects that use it), it can be used.
241Specifically, a virtual type is ``bound" to a virtual table that
242sets the virtual members for that object. The virtual members can be accessed
243through the object.
245This means virtual tables are declared and named in \CFA.
246They are declared as variables, using the type
247@vtable(VIRTUAL_TYPE)@ and any valid name. For example:
249vtable(virtual_type_name) table_name;
252Like any variable, they may be forward declared with the @extern@ keyword.
253Forward declaring virtual tables is relatively common.
254Many virtual types have an ``obvious" implementation that works in most
256A pattern that has appeared in the early work using virtuals is to
257implement a virtual table with the the obvious definition and place a forward
258declaration of it in the header beside the definition of the virtual type.
260Even on the full declaration, no initializer should be used.
261Initialization is automatic.
262The type id and special virtual members ``size" and ``align" only depend on
263the virtual type, which is fixed given the type of the virtual table, and
264so the compiler fills in a fixed value.
265The other virtual members are resolved using the best match to the member's
266name and type, in the same context as the virtual table is declared using
267\CFA's normal resolution rules.
269While much of the virtual infrastructure has been created,
270it is currently only used
271internally for exception handling. The only user-level feature is the virtual
272cast, which is the same as the \Cpp \code{C++}{dynamic_cast}.
277Note, the syntax and semantics matches a C-cast, rather than the function-like
278\Cpp syntax for special casts. Both the type of @EXPRESSION@ and @TYPE@ must be
279pointers to virtual types.
280The cast dynamically checks if the @EXPRESSION@ type is the same or a sub-type
281of @TYPE@, and if true, returns a pointer to the
282@EXPRESSION@ object, otherwise it returns @0p@ (null pointer).
283This allows the expression to be used as both a cast and a type check.
287The syntax for declaring an exception is the same as declaring a structure
288except the keyword:
290exception TYPE_NAME {
291        FIELDS
295Fields are filled in the same way as a structure as well. However, an extra
296field is added that contains the pointer to the virtual table.
297It must be explicitly initialized by the user when the exception is
300Here is an example of declaring an exception type along with a virtual table,
301assuming the exception has an ``obvious" implementation and a default
302virtual table makes sense.
305Header (.hfa):
307exception Example {
308        int data;
311extern vtable(Example)
312        example_base_vtable;
316Implementation (.cfa):
318vtable(Example) example_base_vtable
323%\subsection{Exception Details}
324This is the only interface needed when raising and handling exceptions.
325However, it is actually a shorthand for a more complex
326trait-based interface.
328The language views exceptions through a series of traits.
329If a type satisfies them, then it can be used as an exception. The following
330is the base trait all exceptions need to match.
332trait is_exception(exceptT &, virtualT &) {
333        // Numerous imaginary assertions.
336The trait is defined over two types: the exception type and the virtual table
337type. Each exception type should have a single virtual table type.
338There are no actual assertions in this trait because the trait system
339cannot express them yet (adding such assertions would be part of
340completing the virtual system). The imaginary assertions would probably come
341from a trait defined by the virtual system, and state that the exception type
342is a virtual type,
343that that the type is a descendant of @exception_t@ (the base exception type)
344and allow the user to find the virtual table type.
346% I did have a note about how it is the programmer's responsibility to make
347% sure the function is implemented correctly. But this is true of every
348% similar system I know of (except Agda's I guess) so I took it out.
350There are two more traits for exceptions defined as follows:
352trait is_termination_exception(
353                exceptT &, virtualT & | is_exception(exceptT, virtualT)) {
354        void defaultTerminationHandler(exceptT &);
357trait is_resumption_exception(
358                exceptT &, virtualT & | is_exception(exceptT, virtualT)) {
359        void defaultResumptionHandler(exceptT &);
362Both traits ensure a pair of types is an exception type and
363its virtual table type,
364and defines one of the two default handlers. The default handlers are used
365as fallbacks and are discussed in detail in \autoref{s:ExceptionHandling}.
367However, all three of these traits can be tricky to use directly.
368While there is a bit of repetition required,
369the largest issue is that the virtual table type is mangled and not in a user
370facing way. So, these three macros are provided to wrap these traits to
371simplify referring to the names:
374All three take one or two arguments. The first argument is the name of the
375exception type. The macro passes its unmangled and mangled form to the trait.
376The second (optional) argument is a parenthesized list of polymorphic
377arguments. This argument is only used with polymorphic exceptions and the
378list is passed to both types.
379In the current set-up, the two types always have the same polymorphic
380arguments, so these macros can be used without losing flexibility.
382For example, consider a function that is polymorphic over types that have a
383defined arithmetic exception:
385forall(Num | IS_EXCEPTION(Arithmetic, (Num)))
386void some_math_function(Num & left, Num & right);
389\section{Exception Handling}
391As stated,
392\CFA provides two kinds of exception handling: termination and resumption.
393These twin operations are the core of \CFA's exception handling mechanism.
394This section covers the general patterns shared by the two operations and
395then goes on to cover the details of each individual operation.
397Both operations follow the same set of steps.
398First, a user raises an exception.
399Second, the exception propagates up the stack, searching for a handler.
400Third, if a handler is found, the exception is caught and the handler is run.
401After that control continues at a raise-dependent location.
402As an alternate to the third step,
403if a handler is not found, a default handler is run and, if it returns,
404then control
405continues after the raise.
407The differences between the two operations include how propagation is
408performed, where execution continues after an exception is handled
409and which default handler is run.
413Termination handling is the familiar kind of handling
414used in most programming
415languages with exception handling.
416It is a dynamic, non-local goto. If the raised exception is matched and
417handled, the stack is unwound and control (usually) continues in the function
418on the call stack that defined the handler.
419Termination is commonly used when an error has occurred and recovery is
420impossible locally.
422% (usually) Control can continue in the current function but then a different
423% control flow construct should be used.
425A termination raise is started with the @throw@ statement:
427throw EXPRESSION;
429The expression must return a reference to a termination exception, where the
430termination exception is any type that satisfies the trait
431@is_termination_exception@ at the call site.
432Through \CFA's trait system, the trait functions are implicitly passed into the
433throw code for use by the EHM.
434A new @defaultTerminationHandler@ can be defined in any scope to
435change the throw's behaviour when a handler is not found (see below).
437The throw copies the provided exception into managed memory to ensure
438the exception is not destroyed if the stack is unwound.
439It is the user's responsibility to ensure the original exception is cleaned
440up whether the stack is unwound or not. Allocating it on the stack is
441usually sufficient.
443% How to say propagation starts, its first sub-step is the search.
444Then propagation starts with the search. \CFA uses a ``first match" rule so
445matching is performed with the copied exception as the search key.
446It starts from the raise site and proceeds towards base of the stack,
447from callee to caller.
448At each stack frame, a check is made for termination handlers defined by the
449@catch@ clauses of a @try@ statement.
451try {
452        GUARDED_BLOCK
453} catch (EXCEPTION_TYPE$\(_1\)$ * [NAME$\(_1\)$]) {
454        HANDLER_BLOCK$\(_1\)$
455} catch (EXCEPTION_TYPE$\(_2\)$ * [NAME$\(_2\)$]) {
456        HANDLER_BLOCK$\(_2\)$
459When viewed on its own, a try statement simply executes the statements
460in the \snake{GUARDED_BLOCK} and when those are finished,
461the try statement finishes.
463However, while the guarded statements are being executed, including any
464invoked functions, all the handlers in these statements are included in the
465search path.
466Hence, if a termination exception is raised, these handlers may be matched
467against the exception and may handle it.
469Exception matching checks the handler in each catch clause in the order
470they appear, top to bottom. If the representation of the raised exception type
471is the same or a descendant of @EXCEPTION_TYPE@$_i$, then @NAME@$_i$
472(if provided) is
473bound to a pointer to the exception and the statements in @HANDLER_BLOCK@$_i$
474are executed. If control reaches the end of the handler, the exception is
475freed and control continues after the try statement.
477If no termination handler is found during the search, then the default handler
478(\defaultTerminationHandler) visible at the raise statement is called.
479Through \CFA's trait system the best match at the raise statement is used.
480This function is run and is passed the copied exception.
481If the default handler finishes, control continues after the raise statement.
483There is a global @defaultTerminationHandler@ that is polymorphic over all
484termination exception types.
485The global default termination handler performs a cancellation
486(as described in \vref{s:Cancellation})
487on the current stack with the copied exception.
488Since it is so general, a more specific handler can be defined,
489overriding the default behaviour for the specific exception types.
495Resumption exception handling is less familar form of exception handling,
496but is
497just as old~\cite{Goodenough75} and is simpler in many ways.
498It is a dynamic, non-local function call. If the raised exception is
499matched, a closure is taken from up the stack and executed,
500after which the raising function continues executing.
501The common uses for resumption exceptions include
502potentially repairable errors, where execution can continue in the same
503function once the error is corrected, and
504ignorable events, such as logging where nothing needs to happen and control
505should always continue from the raise site.
507Except for the changes to fit into that pattern, resumption exception
508handling is symmetric with termination exception handling, by design
509(see \autoref{s:Termination}).
511A resumption raise is started with the @throwResume@ statement:
513throwResume EXPRESSION;
515% The new keywords are currently ``experimental" and not used in this work.
516It works much the same way as the termination raise, except the
517type must satisfy the \snake{is_resumption_exception} that uses the
518default handler: \defaultResumptionHandler.
519This can be specialized for particular exception types.
521At run-time, no exception copy is made. Since
522resumption does not unwind the stack nor otherwise remove values from the
523current scope, there is no need to manage memory to keep the exception
526Then propagation starts with the search,
527following the same search path as termination,
528from the raise site to the base of stack and top of try statement to bottom.
529However, the handlers on try statements are defined by @catchResume@ clauses.
531try {
532        GUARDED_BLOCK
533} catchResume (EXCEPTION_TYPE$\(_1\)$ * [NAME$\(_1\)$]) {
534        HANDLER_BLOCK$\(_1\)$
535} catchResume (EXCEPTION_TYPE$\(_2\)$ * [NAME$\(_2\)$]) {
536        HANDLER_BLOCK$\(_2\)$
539Note that termination handlers and resumption handlers may be used together
540in a single try statement, intermixing @catch@ and @catchResume@ freely.
541Each type of handler only interacts with exceptions from the matching
542kind of raise.
543Like @catch@ clauses, @catchResume@ clauses have no effect if an exception
544is not raised.
546The matching rules are exactly the same as well.
547The first major difference here is that after
548@EXCEPTION_TYPE@$_i$ is matched and @NAME@$_i$ is bound to the exception,
549@HANDLER_BLOCK@$_i$ is executed right away without first unwinding the stack.
550After the block has finished running, control jumps to the raise site, where
551the just handled exception came from, and continues executing after it,
552not after the try statement.
555\subsubsection{Resumption Marking}
557A key difference between resumption and termination is that resumption does
558not unwind the stack. A side effect is that, when a handler is matched
559and run, its try block (the guarded statements) and every try statement
560searched before it are still on the stack. Their presence can lead to
561the recursive resumption problem.\cite{Buhr00a}
562% Other possible citation is MacLaren77, but the form is different.
564The recursive resumption problem is any situation where a resumption handler
565ends up being called while it is running.
566Consider a trivial case:
568try {
569        throwResume (E &){};
570} catchResume(E *) {
571        throwResume (E &){};
574When this code is executed, the guarded @throwResume@ starts a
575search and matches the handler in the @catchResume@ clause. This
576call is placed on the stack above the try-block.
577Now the second raise in the handler searches the same try block,
578matches again and then puts another instance of the
579same handler on the stack leading to infinite recursion.
581While this situation is trivial and easy to avoid, much more complex cycles
582can form with multiple handlers and different exception types.
583To prevent all of these cases, each try statement is ``marked" from the
584time the exception search reaches it to either when a handler completes
585handling that exception or when the search reaches the base
586of the stack.
587While a try statement is marked, its handlers are never matched, effectively
588skipping over it to the next try statement.
594There are other sets of marking rules that could be used.
595For instance, marking just the handlers that caught the exception
596would also prevent recursive resumption.
597However, the rules selected mirror what happens with termination,
598so this reduces the amount of rules and patterns a programmer has to know.
600The marked try statements are the ones that would be removed from
601the stack for a termination exception, \ie those on the stack
602between the handler and the raise statement.
603This symmetry applies to the default handler as well, as both kinds of
604default handlers are run at the raise statement, rather than (physically
605or logically) at the bottom of the stack.
606% In early development having the default handler happen after
607% unmarking was just more useful. We assume that will continue.
609\section{Conditional Catch}
610Both termination and resumption handler clauses can be given an additional
611condition to further control which exceptions they handle:
615First, the same semantics is used to match the exception type. Second, if the
616exception matches, @CONDITION@ is executed. The condition expression may
617reference all names in scope at the beginning of the try block and @NAME@
618introduced in the handler clause. If the condition is true, then the handler
619matches. Otherwise, the exception search continues as if the exception type
620did not match.
622The condition matching allows finer matching by checking
623more kinds of information than just the exception type.
625try {
626        handle1 = open( f1, ... );
627        handle2 = open( f2, ... );
628        handle3 = open( f3, ... );
629        ...
630} catch( IOFailure * f ; fd( f ) == f1 ) {
631        // Only handle IO failure for f1.
632} catch( IOFailure * f ; fd( f ) == f3 ) {
633        // Only handle IO failure for f3.
635// Handle a failure relating to f2 further down the stack.
637In this example, the file that experienced the IO error is used to decide
638which handler should be run, if any at all.
641% I know I actually haven't got rid of them yet, but I'm going to try
642% to write it as if I had and see if that makes sense:
645Within the handler block or functions called from the handler block, it is
646possible to reraise the most recently caught exception with @throw@ or
647@throwResume@, respectively.
649try {
650        ...
651} catch( ... ) {
652        ... throw;
653} catchResume( ... ) {
654        ... throwResume;
657The only difference between a raise and a reraise is that reraise does not
658create a new exception; instead it continues using the current exception, \ie
659no allocation and copy. However the default handler is still set to the one
660visible at the raise point, and hence, for termination could refer to data that
661is part of an unwound stack frame. To prevent this problem, a new default
662handler is generated that does a program-level abort.
665\subsection{Comparison with Reraising}
666In languages without conditional catch -- that is, no ability to match an
667exception based on something other than its type -- it can be mimicked
668by matching all exceptions of the right type, checking any additional
669conditions inside the handler and re-raising the exception if it does not
670match those.
672Here is a minimal example comparing both patterns, using @throw;@
673(no operand) to start a re-raise.
675\begin{tabular}{l r}
677try {
678    do_work_may_throw();
679} catch(exception_t * exc ;
680                can_handle(exc)) {
681    handle(exc);
689try {
690    do_work_may_throw();
691} catch(exception_t * exc) {
692    if (can_handle(exc)) {
693        handle(exc);
694    } else {
695        throw;
696    }
701At first glance, catch-and-reraise may appear to just be a quality-of-life
702feature, but there are some significant differences between the two
705A simple difference that is more important for \CFA than many other languages
706is that the raise site changes with a re-raise, but does not with a
707conditional catch.
708This is important in \CFA because control returns to the raise site to run
709the per-site default handler. Because of this, only a conditional catch can
710allow the original raise to continue.
712The more complex issue comes from the difference in how conditional
713catches and re-raises handle multiple handlers attached to a single try
714statement. A conditional catch will continue checking later handlers while
715a re-raise will skip them.
716If the different handlers could handle some of the same exceptions,
717translating a try statement that uses one to use the other can quickly
718become non-trivial:
721Original, with conditional catch:
724} catch (an_exception * e ; check_a(e)) {
725        handle_a(e);
726} catch (exception_t * e ; check_b(e)) {
727        handle_b(e);
730Translated, with re-raise:
733} catch (exception_t * e) {
734        an_exception * an_e = (virtual an_exception *)e;
735        if (an_e && check_a(an_e)) {
736                handle_a(an_e);
737        } else if (check_b(e)) {
738                handle_b(e);
739        } else {
740                throw;
741        }
744(There is a simpler solution if @handle_a@ never raises exceptions,
745using nested try statements.)
747% } catch (an_exception * e ; check_a(e)) {
748%     handle_a(e);
749% } catch (exception_t * e ; !(virtual an_exception *)e && check_b(e)) {
750%     handle_b(e);
751% }
753% } catch (an_exception * e)
754%   if (check_a(e)) {
755%     handle_a(e);
756%   } else throw;
757% } catch (exception_t * e)
758%   if (check_b(e)) {
759%     handle_b(e);
760%   } else throw;
761% }
762In similar simple examples, translating from re-raise to conditional catch
763takes less code but it does not have a general, trivial solution either.
765So, given that the two patterns do not trivially translate into each other,
766it becomes a matter of which on should be encouraged and made the default.
767From the premise that if a handler could handle an exception then it
768should, it follows that checking as many handlers as possible is preferred.
769So, conditional catch and checking later handlers is a good default.
771\section{Finally Clauses}
773Finally clauses are used to perform unconditional cleanup when leaving a
774scope and are placed at the end of a try statement after any handler clauses:
776try {
777        GUARDED_BLOCK
778} ... // any number or kind of handler clauses
779... finally {
780        FINALLY_BLOCK
783The @FINALLY_BLOCK@ is executed when the try statement is removed from the
784stack, including when the @GUARDED_BLOCK@ finishes, any termination handler
785finishes or during an unwind.
786The only time the block is not executed is if the program is exited before
787the stack is unwound.
789Execution of the finally block should always finish, meaning control runs off
790the end of the block. This requirement ensures control always continues as if
791the finally clause is not present, \ie finally is for cleanup, not changing
792control flow.
793Because of this requirement, local control flow out of the finally block
794is forbidden. The compiler precludes any @break@, @continue@, @fallthru@ or
795@return@ that causes control to leave the finally block. Other ways to leave
796the finally block, such as a @longjmp@ or termination are much harder to check,
797and at best require additional run-time overhead, and so are only
800Not all languages with unwinding have finally clauses. Notably, \Cpp does
801without it as destructors, and the RAII design pattern, serve a similar role.
802Although destructors and finally clauses can be used for the same cases,
803they have their own strengths, similar to top-level function and lambda
804functions with closures.
805Destructors take more work to create, but if there is clean-up code
806that needs to be run every time a type is used, they are much easier
807to set up for each use. % It's automatic.
808On the other hand, finally clauses capture the local context, so are easy to
809use when the cleanup is not dependent on the type of a variable or requires
810information from multiple variables.
814Cancellation is a stack-level abort, which can be thought of as as an
815uncatchable termination. It unwinds the entire current stack, and if
816possible, forwards the cancellation exception to a different stack.
818Cancellation is not an exception operation like termination or resumption.
819There is no special statement for starting a cancellation; instead the standard
820library function @cancel_stack@ is called, passing an exception. Unlike a
821raise, this exception is not used in matching, only to pass information about
822the cause of the cancellation.
823Finally, as no handler is provided, there is no default handler.
825After @cancel_stack@ is called, the exception is copied into the EHM's memory
826and the current stack is unwound.
827The behaviour after that depends on the kind of stack being cancelled.
829\paragraph{Main Stack}
830The main stack is the one used by
831the program's main function at the start of execution,
832and is the only stack in a sequential program.
833After the main stack is unwound, there is a program-level abort.
835The first reason for this behaviour is for sequential programs where there
836is only one stack, and hence no stack to pass information to.
837Second, even in concurrent programs, the main stack has no dependency
838on another stack and no reliable way to find another living stack.
839Finally, keeping the same behaviour in both sequential and concurrent
840programs is simple and easy to understand.
842\paragraph{Thread Stack}
843A thread stack is created for a \CFA @thread@ object or object that satisfies
844the @is_thread@ trait.
845After a thread stack is unwound, the exception is stored until another
846thread attempts to join with it. Then the exception @ThreadCancelled@,
847which stores a reference to the thread and to the exception passed to the
848cancellation, is reported from the join to the joining thread.
849There is one difference between an explicit join (with the @join@ function)
850and an implicit join (from a destructor call). The explicit join takes the
851default handler (@defaultResumptionHandler@) from its calling context while
852the implicit join provides its own, which does a program abort if the
853@ThreadCancelled@ exception cannot be handled.
855The communication and synchronization are done here because threads only have
856two structural points (not dependent on user-code) where
857communication/synchronization happens: start and join.
858Since a thread must be running to perform a cancellation (and cannot be
859cancelled from another stack), the cancellation must be after start and
860before the join, so join is used.
862% TODO: Find somewhere to discuss unwind collisions.
863The difference between the explicit and implicit join is for safety and
864debugging. It helps prevent unwinding collisions by avoiding throwing from
865a destructor and prevents cascading the error across multiple threads if
866the user is not equipped to deal with it.
867It is always possible to add an explicit join if that is the desired behaviour.
869With explicit join and a default handler that triggers a cancellation, it is
870possible to cascade an error across any number of threads,
871alternating between the resumption (possibly termination) and cancellation,
872cleaning up each
873in turn, until the error is handled or the main thread is reached.
875\paragraph{Coroutine Stack}
876A coroutine stack is created for a @coroutine@ object or object that
877satisfies the @is_coroutine@ trait.
878After a coroutine stack is unwound, control returns to the @resume@ function
879that most recently resumed it. @resume@ reports a
880@CoroutineCancelled@ exception, which contains a references to the cancelled
881coroutine and the exception used to cancel it.
882The @resume@ function also takes the \defaultResumptionHandler{} from the
883caller's context and passes it to the internal report.
885A coroutine only knows of two other coroutines,
886its starter and its last resumer.
887The starter has a much more distant connection, while the last resumer just
888(in terms of coroutine state) called resume on this coroutine, so the message
889is passed to the latter.
891With a default handler that triggers a cancellation, it is possible to
892cascade an error across any number of coroutines,
893alternating between the resumption (possibly termination) and cancellation,
894cleaning up each in turn,
895until the error is handled or a thread stack is reached.
Note: See TracBrowser for help on using the repository browser.