Version 1.2 of ais/ai-00121.txt
!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)
Replace the paragraph:
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.
by:
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)
Insert after the paragraph:
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.
the new paragraph:
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 <guerby@gnat.com>, 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 <guerby@gnat.com>, Team Ada.
> "Use the Source, Luke. The Source will be with you, always (GPL)."
****************************************************************
Questions? Ask the ACAA Technical Agent