!standard C.3.1 (07) 99-07-28 AI95-00121/05 !class binding interpretation 96-04-04 !status Corrigendum 2000 99-07-28 !status WG9 approved 96-12-07 !status ARG approved 11-0-1 96-10-07 !status work item (letter ballot was 10-1-1) 96-10-03 !status ARG approved 9-0-0 (subject to letter ballot) 96-06-17 !status work item 96-04-04 !status received 96-04-04 !priority High !difficulty Hard !subject Pragma Attach_Handler on Nested Objects !summary A program execution is erroneous if the handlers for a given interrupt attached via pragma Attach_Handler are not attached and detached in a stack-like (LIFO) order. In particular, when a protected object is finalized, if any of its procedures are attached to interrupts via pragma Attach_Handler, then if the most recently attached handler for the same interrupt is not the same as the one that was attached at the time the protected object was created, then execution is erroneous. !question C.3.1(7-8) says: 7 The Attach_Handler pragma is only allowed immediately within the protected_definition where the corresponding subprogram is declared. The corresponding protected_type_declaration or single_protected_declaration shall be a library level declaration. 7.a Discussion: In the case of a protected_type_declaration, an object_declaration of an object of that type need not be at library level. 8 The Interrupt_Handler pragma is only allowed immediately within a protected_definition. The corresponding protected_type_declaration shall be a library level declaration. In addition, any object_declaration of such a type shall be a library level declaration. Thus, nested objects are not allowed in the Interrupt_Handler case, but they are allowed in the Attach_Handler case. C.3.1(12) says: 12 {finalization (of a protected object)} When a protected object is finalized, for any of its procedures that are attached to interrupts, the handler is detached. If the handler was attached by a procedure in the Interrupts package or if no user handler was previously attached to the interrupt, the default treatment is restored. Otherwise, [that is, if an Attach_Handler pragma was used,] the previous handler is restored. 12.a Discussion: Since only library-level protected procedures can be attached as handlers using the Interrupts package, the finalization discussed above occurs only as part of the finalization of all library-level packages in a partition. Thus, in the Attach_Handler case, when the object is finalized, the "previous handler" is restored. What is meant by "previous handler" here? Does this feature make sense in a multi-tasking situation? !recommendation (See summary.) !wording (See corrigendum.) !discussion The notion of restoring the "previous handler" only makes sense if objects are created and destroyed in a stack-like (LIFO) manner. In a multi-tasking program, it is possible to do otherwise -- for example, task A declares an object, then task B declares an object, then task A completes, destroying the first object, then task B completes, destroying the second object. Several options exist: Option 1: require every protected object with an Attach_Handler pragma to be at library level. This is clearly not what the RM says. It doesn't completely solve the problem, either -- one could create two objects on the heap, and Unchecked_Deallocate them in a non-LIFO order. Option 2: define "previous handler" to be "the handler that was attached at the time the protected object was initialized". If that handler no longer exists, execution becomes erroneous. This means that if the programmer uses a LIFO order, it all works. If the programmer uses a non-LIFO order, handlers may get restored in a "surprising" order, and in *some* cases, erroneous execution will result. Note that it is possible to have a LIFO order, even in a multi-tasking program. For example, first declare an object at library level. Create lots of tasks. Then, at some point, one of the tasks declares another object. Clearly, this second object will be finalized before the first one, which is what we want. The implementation in this case is not so hard: store a pointer to the previous handler in the protected object, and blindly restore it on finalization. Option 3: define the "previous handler" to be the one that was attached just before the current handler was attached. Again, execution is erroneous if one tries to restore a handler that no longer exists. Again, the implementation is not so hard: keep a stack of handlers. When a protected object is finalized, blindly pop one item off the stack, whether or not the protected object on the stack corresponds to the current handler. If the programmer ensures a LIFO order, then the second and third possibilities are equivalent. Option 4: an exception is raised if a LIFO order is not obeyed. That is, when a protected object is finalized, a check is made that this protected object corresponds to the currently-attached handler; if not, an exception is raised. In this case, the implementation can be as for the second *or* the third possibility, since they are equivalent. Option 5: same as the fourth, except that execution becomes erroneous instead of raising an exception. The implementation is the same as for the fourth possibility, except that the check is omitted. We choose Option 5. It is the programmer's responsibility to maintain LIFO order; otherwise execution is erroneous. We do not wish to impose overhead on implementations to check for LIFO order. If an implementation wishes to check, it can raise an exception as soon as LIFO order is disobeyed (thus implementing Option 4). We also do not wish to be restrictive, as would happen with Option 1. Upon finalization, an implementation may restore either the handler that was installed at the time the current object was initialized, or the handler that was most recently installed before the current one. For all non-erroneous situations, these two are the same handler. !corrigendum C.03.01(12) @drepl When a protected object is finalized, for any of its procedures that are attached to interrupts, the handler is detached. If the handler was attached by a procedure in the Interrupts package or if no user handler was previously attached to the interrupt, the default treatment is restored. Otherwise, that is, if an Attach_Handler pragma was used, the previous handler is restored. @dby When a protected object is finalized, for any of its procedures that are attached to interrupts, the handler is detached. If the handler was attached by a procedure in the Interrupts package or if no user handler was previously attached to the interrupt, the default treatment is restored. Otherwise, that is, if an Attach_Handler pragma was used and the most recently attached handler for the same interrupt is the same as the one that was attached at the time the protected object was created, the previous handler is restored. !corrigendum C.03.01(14) @dinsa If the Ceiling_Locking policy (see D.3) is in effect and an interrupt is delivered to a handler, and the interrupt hardware priority is higher than the ceiling priority of the corresponding protected object, the execution of the program is erroneous. @dinst If the handlers for a given interrupt attached via pragma Attach_Handler are not attached and detached in a stack-like (LIFO) order, program execution is erroneous. In particular, when a protected object is finalized, if any of its procedures are attached to interrupts via pragma Attach_Handler, then if the most recently attached handler for the same interrupt is not the same as the one that was attached at the time the protected object was created, the execution is erroneous. !ACATS test This ruling makes some executions of Attach_Handler erroneous, so it is not testable. !appendix !section C.3.1(07) !subject Nesting of statically-attached interrupt handlers does not make sense !reference RM95-C.3.1(7) !reference RM95-C.3.1(8) !reference RM95-C.3.1(10) !reference RM95-C.3.1(12) !from Offer Pazy 95-12-11 !reference 95-5413.a Offer Pazy 95-12-11>> !discussion Disclaimer: ----------- I am not sure that this comment fits the formal definition of an AI. The semantics are very clear. As I explain below, I have just come to realize that they were not fully thought about and there are usability and implementation problems. Background: ----------- One of the design goals of the System Programming Annex was to allow the nesting of interrupt handlers. That is, we envisioned a requirement, for large, multi-mode, systems, in which different modules are responsible for a given interrupt at different times, and hence different handlers need to be attached, detached, and restored(!) dynamically. In general, the idea in Ada in to limit the places where limitations are placed on non-library level code. That is, everything that is possible for multi-mode system which is organized as library-level subsystems should also be possible when the modes are implemented as nested in one another. Anyway, this was the rationale behind the above feature: A new mode (which is nested in another) gets invoked, takes control of some of the interrupts, and when it exits, responsibility for those interrupts is restored to the enclosing mode, transparently to that mode (that is, the handlers are saved, a new one is attached, and then the previous one is restored). The natural way to express this in Ada was through the elaboration and finalization of nested protected objects that have the pragma Attach_Handler applied to one of their procedures. Note a little-known rule in (7,8). The rules for dynamic attachment and static attachment are different on purpose. For both, the protected type needs to be at a library level, but for the dynamic attachment (through the Interrupts package), the protected object itself must also be at the library level, while for the static attachment, the object can be deeper. The rationale (C.3.1) discusses these issues and the reasons of why we did not allow nesting in the dynamic case. The problem: ------------ The problem is that this all idea is not fully thought about. I think we had (mostly I had) a temporary fading of the brain in the sense that Ada appear to be a multi-tasking environment and in such an environment the above model does not make much sense. "Modes" are not necessarily nested within tasks. While a given scope is entered in one task, other tasks may still be running "in a scope" that still has attached interrupts. What will happen is that one task will "steel" the interrupt from another without the latter knowing about it, thus affecting the assumed environment of that task. This is one of the worst cases of unsynchronized use of shared variables where the shared variable in this case is the HW component connected to the interrupt. A "correct", but also impractical model would be that the attachment of interrupts are assumed part of the task context and are therefore being switched in and out on each context-switch. This is clearly undesired and not very feasible. Imagine task T1 talking to some piece of hardware, sending some request and then waiting for the response; task T2 then is dispatched and it elaborates a PO to be the handler of the corresponding interrupt. T2 now receives a meaningless interrupt while T1 loses its. Since this is affected by the preemption and scheduling rules, it is largely asynchronous. We did not specify any synchronization (or signalling) rules to require a disciplined usage of this capability, not that I see a simple way to define such rules. One may claim that this is all a user problem and the designer should make arrangements so this will never happen. While correct in principle, we don't provide mechanisms to coordinate this. Any safe usage of this feature will require resorting to either one task in the partition doing the attachment/detachment, or using some other guidelines that essentially reduce the multi-tasking to a single task (or coroutines implemented as multiple tasks). Nested attachment of handlers would make perfect sense in a single tasking environment. But if this is the limitation, it can already be done with the dynamic form of attachment. Implementing this requirement is also not trivial. Never mind, the useless semantics, some sort of two-dimensional doubly linked-list has to be implemented. This is because there is no guarantee that handles will be attached/detached in a LIFO order. When a PO (with statically-attached handlers) finalizes, it is not guaranteed that the given handler was the last one to be attached for that interrupt. The handler may be anywhere in the linked list and will have to be removed from the middle of the list emanated from the interrupt. Not trivial in particular when it doe snot have any real benefit. Also, C.3.1(12) has a problem: It ends with "... the previous handler is restored." What is the previous handler? The one that was active the last? It may already have gone by now, Also, when a PO finalizes, it is not always the case, that "its" handler is the current one, so it will not be always correct to affect the current attachment. The solution(?): ---------------- While it is alway embarrassing to acknowledge a mistake, and it's a pity to give up on a seemingly-useful feature, I don't see any other alternative but to admit the mistake. I don't see a way that the ARM language could be re-interpreted to "quietly" solve this problem. Maybe the best thing is to modify the above references and to disallow non-library level objects for the static case as well. Whatever the solution is, we should make sure that we don't lose the other feature of statically-attached handlers, and that is the ability to support attachment at pre-elaboration time (i.e. for library-level objects). I think that it is still important to be able to ensure that interrupt handlers are installed as early as possible in the program startup. Offer Pazy 31 Robinwood Ave. Boston, MA 02130 USA (617)522-5988 pazy@world.std.com **************************************************************** !section C.3.1(12) !subject Attach_Handler and finalization !reference RM95-C.3.1(12) !from Laurent Guerby 97-08-08 !keywords interrupts, finalization !reference 1997-15773.a Laurent Guerby 1997-8-8>> !discussion C.3.1: | 12 {finalization (of a protected object)} When a protected object is | finalized, for any of its procedures that are attached to interrupts, the | handler is detached. If the handler was attached by a procedure in the | Interrupts package or if no user handler was previously attached to the | interrupt, the default treatment is restored. Otherwise, [that is, if an | Attach_Handler pragma was used,] the previous handler is restored. | 12.a Discussion: Since only library-level protected procedures can | be attached as handlers using the Interrupts package, the | finalization discussed above occurs only as part of the finalization | of all library-level packages in a partition. The 12.a annotation is partially incorrect since you can get non-library level protected objects requiring such finalization by declaring an object of a protected type with an Attach_Handler pragma. (RM forbids only non-library level objects with a pragma Interrupt_Handler in them.) Is this intented? It forces implementation to keep somehow a stack of "previous handlers" for non library-level PO with Attach_Handler, that sounds very dynamic for a "static" feature (Attach_Handler needs more dynamic work than Ada.Interrupt and Interrupt_Handler...). -- Laurent Guerby , Team Ada. "Use the Source, Luke. The Source will be with you, always (GPL)." **************************************************************** !section C.3.1(12) !subject Attach_Handler and finalization !reference RM95-C.3.1(12) !reference 1997-15773.a Laurent Guerby 97-08-08 !reference AI95-00121 !keywords interrupts, finalization !from Tucker Taft 97-08-11 !reference 1997-15775.a Tucker Taft 1997-8-11>> !discussion This problem is addressed by AI95-00121 (approved by WG9), which says: A program execution is erroneous if the handlers for a given interrupts attached via pragma Attach_Handler are not attached and detached in a stack-like (LIFO) order. ... Hence, the implementation need not do anything special to handle the case where finalizations occur in a non stack-like order. It is possible to create a doubly linked list of handlers, and survive detaching of the non-current handler for an interrupt, but it is not necessary for the implementation to do so. -Tuck -------------------- > C.3.1: > > | 12 {finalization (of a protected object)} When a protected object is > | finalized, for any of its procedures that are attached to interrupts, the > | handler is detached. If the handler was attached by a procedure in the > | Interrupts package or if no user handler was previously attached to the > | interrupt, the default treatment is restored. Otherwise, [that is, if an > | Attach_Handler pragma was used,] the previous handler is restored. > > | 12.a Discussion: Since only library-level protected procedures can > | be attached as handlers using the Interrupts package, the > | finalization discussed above occurs only as part of the finalization > | of all library-level packages in a partition. > > The 12.a annotation is partially incorrect since you can get > non-library level protected objects requiring such finalization by > declaring an object of a protected type with an Attach_Handler pragma. > (RM forbids only non-library level objects with a pragma > Interrupt_Handler in them.) > > Is this intented? It forces implementation to keep somehow a stack > of "previous handlers" for non library-level PO with Attach_Handler, > that sounds very dynamic for a "static" feature (Attach_Handler needs > more dynamic work than Ada.Interrupt and Interrupt_Handler...). > > -- > Laurent Guerby , Team Ada. > "Use the Source, Luke. The Source will be with you, always (GPL)." ****************************************************************