source: doc/theses/andrew_beach_MMath/features.tex @ 4aba055

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

Andrew MMath: Folded in features feedback and redid the reraise comparison. (3/3 for this group.)

  • Property mode set to 100644
File size: 33.8 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 also 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 the language.
21The raise is the starting point for exception handling. It marks the beginning
22of exception handling by 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 from Python. In real systems a raise may
27preform some other work (such as memory management) but for the
28purposes of this overview that can be ignored.
31The purpose of most exception operations is to run some user code to handle
32that exception. This code is given, with some other information, in a handler.
34A handler has three common features: the previously mentioned user code, a
35region of code they guard and an exception label/condition that matches
36certain exceptions.
37Only raises inside the guarded region and raising exceptions that match the
38label can be handled by a given handler.
39Different EHMs use different rules to pick a handler,
40if multiple handlers could be used such as ``best match" or ``first found".
42The @try@ statements of \Cpp, Java and Python are common examples. All three
43also show another common feature of handlers, they are grouped by the guarded
47After an exception is raised comes what is usually the biggest step for the
48EHM: finding and setting up the handler. The propagation from raise to
49handler can be broken up into three different tasks: searching for a handler,
50matching against the handler and installing the handler.
53The EHM begins by searching for handlers that might be used to handle
54the exception. Searching is usually independent of the exception that was
55thrown as it looks for handlers that have the raise site in their guarded
57The search includes handlers in the current function, as well as any in
58callers on the stack that have the function call in their guarded region.
61Each handler found has to be matched with the raised exception. The exception
62label defines a condition that is used with exception and decides if
63there is a match or not.
65In languages where the first match is used, this step is intertwined with
66searching; a match check is preformed immediately after the search finds
67a possible handler.
70After a handler is chosen it must be made ready to run.
71The implementation can vary widely to fit with the rest of the
72design of the EHM. The installation step might be trivial or it could be
73the most expensive step in handling an exception. The latter tends to be the
74case when stack unwinding is involved.
76If a matching handler is not guarantied to be found, the EHM needs a
77different course of action for the case where no handler matches.
78This situation only occurs with unchecked exceptions as checked exceptions
79(such as in Java) can make the guarantee.
80This unhandled action is usually very general, such as aborting the program.
83A common way to organize exceptions is in a hierarchical structure.
84This pattern comes from object-orientated languages where the
85exception hierarchy is a natural extension of the object hierarchy.
87Consider the following hierarchy of exceptions:
92A handler labeled with any given exception can handle exceptions of that
93type or any child type of that exception. The root of the exception hierarchy
94(here \code{C}{exception}) acts as a catch-all, leaf types catch single types
95and the exceptions in the middle can be used to catch different groups of
96related exceptions.
98This system has some notable advantages, such as multiple levels of grouping,
99the ability for libraries to add new exception types and the isolation
100between different sub-hierarchies.
101This design is used in \CFA even though it is not a object-orientated
102language; so different tools are used to create the hierarchy.
104% Could I cite the rational for the Python IO exception rework?
107After the handler has finished the entire exception operation has to complete
108and continue executing somewhere else. This step is usually simple,
109both logically and in its implementation, as the installation of the handler
110is usually set up to do most of the work.
112The EHM can return control to many different places,
113the most common are after the handler definition (termination)
114and after the raise (resumption).
117For effective exception handling, additional information is often passed
118from the raise to the handler and back again.
119So far only communication of the exceptions' identity has been covered.
120A common communication method is putting fields into the exception instance
121and giving the handler access to them.
122Passing the exception by reference instead of by value can allow data to be
123passed in both directions.
126Virtual types and casts are not part of \CFA's EHM nor are they required for
127any EHM.
128However, it is one of the best ways to support an exception hierachy
129is via a virtual hierarchy and dispatch system.
131Ideally, the virtual system would have been part of \CFA before the work
132on exception handling began, but unfortunately it was not.
133Hence, only the features and framework needed for the EHM were
134designed and implemented. Other features were considered to ensure that
135the structure could accommodate other desirable features in the future
136but they were not implemented.
137The rest of this section will only discuss the implemented subset of the
138virtual system design.
140The virtual system supports multiple ``trees" of types. Each tree is
141a simple hierarchy with a single root type. Each type in a tree has exactly
142one parent -- except for the root type which has zero parents -- and any
143number of children.
144Any type that belongs to any of these trees is called a virtual type.
146% A type's ancestors are its parent and its parent's ancestors.
147% The root type has no ancestors.
148% A type's descendants are its children and its children's descendants.
150Every virtual type also has a list of virtual members. Children inherit
151their parent's list of virtual members but may add new members to it.
152It is important to note that these are virtual members, not virtual methods
153of object-orientated programming, and can be of any type.
155\CFA still supports virtual methods as a special case of virtual members.
156Function pointers that take a pointer to the virtual type are modified
157with each level of inheritance so that refers to the new type.
158This means an object can always be passed to a function in its virtual table
159as if it were a method.
160\todo{Clarify (with an example) virtual methods.}
162Each virtual type has a unique id.
163This id and all the virtual members are combined
164into a virtual table type. Each virtual type has a pointer to a virtual table
165as a hidden field.
166\todo{Might need a diagram for virtual structure.}
168Up until this point the virtual system is similar to ones found in
169object-orientated languages but this where \CFA diverges. Objects encapsulate a
170single set of behaviours in each type, universally across the entire program,
171and indeed all programs that use that type definition. In this sense, the
172types are ``closed" and cannot be altered.
174In \CFA, types do not encapsulate any behaviour. Traits are local and
175types can begin to satisfy a trait, stop satisfying a trait or satisfy the same
176trait in a different way at any lexical location in the program.
177In this sense, they are ``open" as they can change at any time.
178This capability means it is impossible to pick a single set of functions
179that represent the type's implementation across the program.
181\CFA side-steps this issue by not having a single virtual table for each
182type. A user can define virtual tables that are filled in at their
183declaration and given a name. Anywhere that name is visible, even if it is
184defined locally inside a function (although that means it does not have a
185static lifetime), it can be used.
186Specifically, a virtual type is ``bound" to a virtual table that
187sets the virtual members for that object. The virtual members can be accessed
188through the object.
190While much of the virtual infrastructure is created, it is currently only used
191internally for exception handling. The only user-level feature is the virtual
192cast, which is the same as the \Cpp \code{C++}{dynamic_cast}.
197Note, the syntax and semantics matches a C-cast, rather than the function-like
198\Cpp syntax for special casts. Both the type of @EXPRESSION@ and @TYPE@ must be
199a pointer to a virtual type.
200The cast dynamically checks if the @EXPRESSION@ type is the same or a sub-type
201of @TYPE@, and if true, returns a pointer to the
202@EXPRESSION@ object, otherwise it returns @0p@ (null pointer).
205% Leaving until later, hopefully it can talk about actual syntax instead
206% of my many strange macros. Syntax aside I will also have to talk about the
207% features all exceptions support.
209Exceptions are defined by the trait system; there are a series of traits, and
210if a type satisfies them, then it can be used as an exception. The following
211is the base trait all exceptions need to match.
213trait is_exception(exceptT &, virtualT &) {
214        // Numerous imaginary assertions.
217The trait is defined over two types, the exception type and the virtual table
218type. Each exception type should have a single virtual table type.
219There are no actual assertions in this trait because the trait system
220cannot express them yet (adding such assertions would be part of
221completing the virtual system). The imaginary assertions would probably come
222from a trait defined by the virtual system, and state that the exception type
223is a virtual type, is a descendant of @exception_t@ (the base exception type)
224and note its virtual table type.
226% I did have a note about how it is the programmer's responsibility to make
227% sure the function is implemented correctly. But this is true of every
228% similar system I know of (except Agda's I guess) so I took it out.
230There are two more traits for exceptions defined as follows:
232trait is_termination_exception(
233                exceptT &, virtualT & | is_exception(exceptT, virtualT)) {
234        void defaultTerminationHandler(exceptT &);
237trait is_resumption_exception(
238                exceptT &, virtualT & | is_exception(exceptT, virtualT)) {
239        void defaultResumptionHandler(exceptT &);
242Both traits ensure a pair of types are an exception type, its virtual table
244and defines one of the two default handlers. The default handlers are used
245as fallbacks and are discussed in detail in \vref{s:ExceptionHandling}.
247However, all three of these traits can be tricky to use directly.
248While there is a bit of repetition required,
249the largest issue is that the virtual table type is mangled and not in a user
250facing way. So these three macros are provided to wrap these traits to
251simplify referring to the names:
254All three take one or two arguments. The first argument is the name of the
255exception type. The macro passes its unmangled and mangled form to the trait.
256The second (optional) argument is a parenthesized list of polymorphic
257arguments. This argument is only used with polymorphic exceptions and the
258list is be passed to both types.
259In the current set-up, the two types always have the same polymorphic
260arguments so these macros can be used without losing flexibility.
262For example consider a function that is polymorphic over types that have a
263defined arithmetic exception:
265forall(Num | IS_EXCEPTION(Arithmetic, (Num)))
266void some_math_function(Num & left, Num & right);
269\section{Exception Handling}
271As stated,
272\CFA provides two kinds of exception handling: termination and resumption.
273These twin operations are the core of \CFA's exception handling mechanism.
274This section will cover the general patterns shared by the two operations and
275then go on to cover the details each individual operation.
277Both operations follow the same set of steps.
278Both start with the user preforming a raise on an exception.
279Then the exception propagates up the stack.
280If a handler is found the exception is caught and the handler is run.
281After that control continues at a raise-dependent location.
282If the search fails a default handler is run and, if it returns, then control
283continues after the raise.
285This general description covers what the two kinds have in common.
286Differences include how propagation is preformed, where exception continues
287after an exception is caught and handled and which default handler is run.
291Termination handling is the familiar kind and used in most programming
292languages with exception handling.
293It is a dynamic, non-local goto. If the raised exception is matched and
294handled, the stack is unwound and control (usually) continues in the function
295on the call stack that defined the handler.
296Termination is commonly used when an error has occurred and recovery is
297impossible locally.
299% (usually) Control can continue in the current function but then a different
300% control flow construct should be used.
302A termination raise is started with the @throw@ statement:
304throw EXPRESSION;
306The expression must return a reference to a termination exception, where the
307termination exception is any type that satisfies the trait
308@is_termination_exception@ at the call site.
309Through \CFA's trait system, the trait functions are implicitly passed into the
310throw code and the EHM.
311A new @defaultTerminationHandler@ can be defined in any scope to
312change the throw's behaviour (see below).
314The throw copies the provided exception into managed memory to ensure
315the exception is not destroyed if the stack is unwound.
316It is the user's responsibility to ensure the original exception is cleaned
317up whether the stack is unwound or not. Allocating it on the stack is
318usually sufficient.
320% How to say propagation starts, its first sub-step is the search.
321Then propagation starts with the search. \CFA uses a ``first match" rule so
322matching is preformed with the copied exception as the search continues.
323It starts from the throwing function and proceeds towards base of the stack,
324from callee to caller.
325At each stack frame, a check is made for resumption handlers defined by the
326@catch@ clauses of a @try@ statement.
328try {
329        GUARDED_BLOCK
330} catch (EXCEPTION_TYPE$\(_1\)$ * [NAME$\(_1\)$]) {
331        HANDLER_BLOCK$\(_1\)$
332} catch (EXCEPTION_TYPE$\(_2\)$ * [NAME$\(_2\)$]) {
333        HANDLER_BLOCK$\(_2\)$
336When viewed on its own, a try statement simply executes the statements
337in \snake{GUARDED_BLOCK} and when those are finished,
338the try statement finishes.
340However, while the guarded statements are being executed, including any
341invoked functions, all the handlers in these statements are included in the
342search path.
343Hence, if a termination exception is raised these handlers may be matched
344against the exception and may handle it.
346Exception matching checks the handler in each catch clause in the order
347they appear, top to bottom. If the representation of the raised exception type
348is the same or a descendant of @EXCEPTION_TYPE@$_i$ then @NAME@$_i$
349(if provided) is
350bound to a pointer to the exception and the statements in @HANDLER_BLOCK@$_i$
351are executed. If control reaches the end of the handler, the exception is
352freed and control continues after the try statement.
354If no termination handler is found during the search then the default handler
355(\defaultTerminationHandler) visible at the raise statement is run.
356Through \CFA's trait system the best match at the raise statement will be used.
357This function is run and is passed the copied exception.
358If the default handler is run control continues after the raise statement.
360There is a global @defaultTerminationHandler@ that is polymorphic over all
361termination exception types.
362Since it is so general a more specific handler can be
363defined and is used for those types, effectively overriding the handler
364for a particular exception type.
365The global default termination handler performs a cancellation
366(see \vref{s:Cancellation}) on the current stack with the copied exception.
371Resumption exception handling is less common than termination but is
372just as old~\cite{Goodenough75} and is simpler in many ways.
373It is a dynamic, non-local function call. If the raised exception is
374matched a closure is taken from up the stack and executed,
375after which the raising function continues executing.
376The common uses for resumption exceptions include
377potentially repairable errors, where execution can continue in the same
378function once the error is corrected, and
379ignorable events, such as logging where nothing needs to happen and control
380should always continue from the same place.
382A resumption raise is started with the @throwResume@ statement:
384throwResume EXPRESSION;
386\todo{Decide on a final set of keywords and use them everywhere.}
387It works much the same way as the termination throw.
388The expression must return a reference to a resumption exception,
389where the resumption exception is any type that satisfies the trait
390@is_resumption_exception@ at the call site.
391The assertions from this trait are available to
392the exception system while handling the exception.
394At run-time, no exception copy is made.
395Resumption does not unwind the stack nor otherwise remove values from the
396current scope, so there is no need to manage memory to keep things in scope.
398The EHM then begins propagation. The search starts from the raise in the
399resuming function and proceeds towards the base of the stack,
400from callee to caller.
401At each stack frame, a check is made for resumption handlers defined by the
402@catchResume@ clauses of a @try@ statement.
404try {
405        GUARDED_BLOCK
406} catchResume (EXCEPTION_TYPE$\(_1\)$ * [NAME$\(_1\)$]) {
407        HANDLER_BLOCK$\(_1\)$
408} catchResume (EXCEPTION_TYPE$\(_2\)$ * [NAME$\(_2\)$]) {
409        HANDLER_BLOCK$\(_2\)$
412% I wonder if there would be some good central place for this.
413Note that termination handlers and resumption handlers may be used together
414in a single try statement, intermixing @catch@ and @catchResume@ freely.
415Each type of handler only interacts with exceptions from the matching
416kind of raise.
417When a try statement is executed, it simply executes the statements in the
418@GUARDED_BLOCK@ and then finishes.
420However, while the guarded statements are being executed, including any
421invoked functions, all the handlers in these statements are included in the
422search path.
423Hence, if a resumption exception is raised these handlers may be matched
424against the exception and may handle it.
426Exception matching checks the handler in each catch clause in the order
427they appear, top to bottom. If the representation of the raised exception type
428is the same or a descendant of @EXCEPTION_TYPE@$_i$ then @NAME@$_i$
429(if provided) is bound to a pointer to the exception and the statements in
430@HANDLER_BLOCK@$_i$ are executed.
431If control reaches the end of the handler, execution continues after the
432the raise statement that raised the handled exception.
434Like termination, if no resumption handler is found during the search,
435the default handler (\defaultResumptionHandler) visible at the raise
436statement is called. It will use the best match at the raise sight according
437to \CFA's overloading rules. The default handler is
438passed the exception given to the raise. When the default handler finishes
439execution continues after the raise statement.
441There is a global \defaultResumptionHandler{} is polymorphic over all
442resumption exceptions and preforms a termination throw on the exception.
443The \defaultTerminationHandler{} can be overriden by providing a new
444function that is a better match.
446\subsubsection{Resumption Marking}
448A key difference between resumption and termination is that resumption does
449not unwind the stack. A side effect that is that when a handler is matched
450and run it's try block (the guarded statements) and every try statement
451searched before it are still on the stack. There presence can lead to
452the recursive resumption problem.
454The recursive resumption problem is any situation where a resumption handler
455ends up being called while it is running.
456Consider a trivial case:
458try {
459        throwResume (E &){};
460} catchResume(E *) {
461        throwResume (E &){};
464When this code is executed, the guarded @throwResume@ starts a
465search and matches the handler in the @catchResume@ clause. This
466call is placed on the stack above the try-block. The second raise then
467searches the same try block and puts another instance of the
468same handler on the stack leading to infinite recursion.
470While this situation is trivial and easy to avoid, much more complex cycles
471can form with multiple handlers and different exception types.
473To prevent all of these cases, a each try statement is ``marked" from the
474time the exception search reaches it to either when the exception is being
475handled completes the matching handler or when the search reaches the base
476of the stack.
477While a try statement is marked, its handlers are never matched, effectively
478skipping over it to the next try statement.
484There are other sets of marking rules that could be used,
485for instance, marking just the handlers that caught the exception,
486would also prevent recursive resumption.
487However, these rules mirror what happens with termination.
489The try statements that are marked are the ones that would be removed from
490the stack if this was a termination exception, that is those on the stack
491between the handler and the raise statement.
492This symmetry applies to the default handler as well, as both kinds of
493default handlers are run at the raise statement, rather than (physically
494or logically) at the bottom of the stack.
495% In early development having the default handler happen after
496% unmarking was just more useful. We assume that will continue.
498\section{Conditional Catch}
499Both termination and resumption handler clauses can be given an additional
500condition to further control which exceptions they handle:
504First, the same semantics is used to match the exception type. Second, if the
505exception matches, @CONDITION@ is executed. The condition expression may
506reference all names in scope at the beginning of the try block and @NAME@
507introduced in the handler clause. If the condition is true, then the handler
508matches. Otherwise, the exception search continues as if the exception type
509did not match.
511The condition matching allows finer matching by checking
512more kinds of information than just the exception type.
514try {
515        handle1 = open( f1, ... );
516        handle2 = open( f2, ... );
517        handle3 = open( f3, ... );
518        ...
519} catch( IOFailure * f ; fd( f ) == f1 ) {
520        // Only handle IO failure for f1.
521} catch( IOFailure * f ; fd( f ) == f3 ) {
522        // Only handle IO failure for f3.
524// Can't handle a failure relating to f2 here.
526In this example the file that experienced the IO error is used to decide
527which handler should be run, if any at all.
530% I know I actually haven't got rid of them yet, but I'm going to try
531% to write it as if I had and see if that makes sense:
534Within the handler block or functions called from the handler block, it is
535possible to reraise the most recently caught exception with @throw@ or
536@throwResume@, respectively.
538try {
539        ...
540} catch( ... ) {
541        ... throw;
542} catchResume( ... ) {
543        ... throwResume;
546The only difference between a raise and a reraise is that reraise does not
547create a new exception; instead it continues using the current exception, \ie
548no allocation and copy. However the default handler is still set to the one
549visible at the raise point, and hence, for termination could refer to data that
550is part of an unwound stack frame. To prevent this problem, a new default
551handler is generated that does a program-level abort.
554\subsection{Comparison with Reraising}
555A more popular way to allow handlers to match in more detail is to reraise
556the exception after it has been caught, if it could not be handled here.
557On the surface these two features seem interchangeable.
559If @throw;@ (no argument) starts a termination reraise,
560which is the same as a raise but reuses the last caught exception,
561then these two statements have the same behaviour:
563try {
564    do_work_may_throw();
565} catch(exception_t * exc ; can_handle(exc)) {
566    handle(exc);
571try {
572    do_work_may_throw();
573} catch(exception_t * exc) {
574    if (can_handle(exc)) {
575        handle(exc);
576    } else {
577        throw;
578    }
581That is, they will have the same behaviour in isolation.
582Two things can expose differences between these cases.
584One is the existance of multiple handlers on a single try statement.
585A reraise skips all later handlers on this try statement but a conditional
586catch does not.
587Hence, if an earlier handler contains a reraise later handlers are
588implicitly skipped, with a conditional catch they are not.
589Still, they are equivalently powerful,
590both can be used two mimick the behaviour of the other,
591as reraise can pack arbitrary code in the handler and conditional catches
592can put arbitrary code in the predicate.
593% I was struggling with a long explination about some simple solutions,
594% like repeating a condition on later handlers, and the general solution of
595% merging everything together. I don't think it is useful though unless its
596% for a proof.
599The question then becomes ``Which is a better default?"
600We believe that not skipping possibly useful handlers is a better default.
601If a handler can handle an exception it should and if the handler can not
602handle the exception then it is probably safer to have that explicitly
603described in the handler itself instead of implicitly described by its
604ordering with other handlers.
605% Or you could just alter the semantics of the throw statement. The handler
606% index is in the exception so you could use it to know where to start
607% searching from in the current try statement.
608% No place for the `goto else;` metaphor.
610The other issue is all of the discussion above assumes that the only
611way to tell apart two raises is the exception being raised and the remaining
612search path.
613This is not true generally, the current state of the stack can matter in
614a number of cases, even only for a stack trace after an program abort.
615But \CFA has a much more significant need of the rest of the stack, the
616default handlers for both termination and resumption.
618% For resumption it turns out it is possible continue a raise after the
619% exception has been caught, as if it hadn't been caught in the first place.
620This becomes a problem combined with the stack unwinding used in termination
621exception handling.
622The stack is unwound before the handler is installed, and hence before any
623reraises can run. So if a reraise happens the previous stack is gone,
624the place on the stack where the default handler was supposed to run is gone,
625if the default handler was a local function it may have been unwound too.
626There is no reasonable way to restore that information, so the reraise has
627to be considered as a new raise.
628This is the strongest advantage conditional catches have over reraising,
629they happen before stack unwinding and avoid this problem.
631% The one possible disadvantage of conditional catch is that it runs user
632% code during the exception search. While this is a new place that user code
633% can be run destructors and finally clauses are already run during the stack
634% unwinding.
637%   `exception_ptr current_exception() noexcept;`
640\section{Finally Clauses}
642Finally clauses are used to preform unconditional clean-up when leaving a
643scope and are placed at the end of a try statement after any handler clauses:
645try {
646        GUARDED_BLOCK
647} ... // any number or kind of handler clauses
648... finally {
649        FINALLY_BLOCK
652The @FINALLY_BLOCK@ is executed when the try statement is removed from the
653stack, including when the @GUARDED_BLOCK@ finishes, any termination handler
654finishes or during an unwind.
655The only time the block is not executed is if the program is exited before
656the stack is unwound.
658Execution of the finally block should always finish, meaning control runs off
659the end of the block. This requirement ensures control always continues as if
660the finally clause is not present, \ie finally is for cleanup not changing
661control flow.
662Because of this requirement, local control flow out of the finally block
663is forbidden. The compiler precludes any @break@, @continue@, @fallthru@ or
664@return@ that causes control to leave the finally block. Other ways to leave
665the finally block, such as a long jump or termination are much harder to check,
666and at best requiring additional run-time overhead, and so are only
669Not all languages with unwinding have finally clauses. Notably \Cpp does
670without it as descructors, and the RAII design pattern, serve a similar role.
671Although destructors and finally clauses can be used in the same cases,
672they have their own strengths, similar to top-level function and lambda
673functions with closures.
674Destructors take more work for their first use, but if there is clean-up code
675that needs to be run every time a type is used they soon become much easier
676to set-up.
677On the other hand finally clauses capture the local context, so is easy to
678use when the clean-up is not dependent on the type of a variable or requires
679information from multiple variables.
680% To Peter: I think these are the main points you were going for.
684Cancellation is a stack-level abort, which can be thought of as as an
685uncatchable termination. It unwinds the entire current stack, and if
686possible forwards the cancellation exception to a different stack.
688Cancellation is not an exception operation like termination or resumption.
689There is no special statement for starting a cancellation; instead the standard
690library function @cancel_stack@ is called passing an exception. Unlike a
691raise, this exception is not used in matching only to pass information about
692the cause of the cancellation.
693(This also means matching cannot fail so there is no default handler.)
695After @cancel_stack@ is called the exception is copied into the EHM's memory
696and the current stack is unwound.
697The behaviour after that depends on the kind of stack being cancelled.
699\paragraph{Main Stack}
700The main stack is the one used by the program main at the start of execution,
701and is the only stack in a sequential program.
702After the main stack is unwound there is a program-level abort.
704There are two reasons for these semantics.
705The first is that it had to do this abort.
706in a sequential program as there is nothing else to notify and the simplicity
707of keeping the same behaviour in sequential and concurrent programs is good.
708Also, even in concurrent programs there may not currently be any other stacks
709and even if other stacks do exist, main has no way to know where they are.
711\paragraph{Thread Stack}
712A thread stack is created for a \CFA @thread@ object or object that satisfies
713the @is_thread@ trait.
714After a thread stack is unwound, the exception is stored until another
715thread attempts to join with it. Then the exception @ThreadCancelled@,
716which stores a reference to the thread and to the exception passed to the
717cancellation, is reported from the join to the joining thread.
718There is one difference between an explicit join (with the @join@ function)
719and an implicit join (from a destructor call). The explicit join takes the
720default handler (@defaultResumptionHandler@) from its calling context while
721the implicit join provides its own; which does a program abort if the
722@ThreadCancelled@ exception cannot be handled.
724The communication and synchronization are done here because threads only have
725two structural points (not dependent on user-code) where
726communication/synchronization happens: start and join.
727Since a thread must be running to perform a cancellation (and cannot be
728cancelled from another stack), the cancellation must be after start and
729before the join, so join is used.
731% TODO: Find somewhere to discuss unwind collisions.
732The difference between the explicit and implicit join is for safety and
733debugging. It helps prevent unwinding collisions by avoiding throwing from
734a destructor and prevents cascading the error across multiple threads if
735the user is not equipped to deal with it.
736Also you can always add an explicit join if that is the desired behaviour.
738\paragraph{Coroutine Stack}
739A coroutine stack is created for a @coroutine@ object or object that
740satisfies the @is_coroutine@ trait.
741After a coroutine stack is unwound, control returns to the @resume@ function
742that most recently resumed it. @resume@ reports a
743@CoroutineCancelled@ exception, which contains a references to the cancelled
744coroutine and the exception used to cancel it.
745The @resume@ function also takes the \defaultResumptionHandler{} from the
746caller's context and passes it to the internal report.
748A coroutine knows of two other coroutines, its starter and its last resumer.
749The starter has a much more distant connection, while the last resumer just
750(in terms of coroutine state) called resume on this coroutine, so the message
751is passed to the latter.
Note: See TracBrowser for help on using the repository browser.