Version 1.4 of ai05s/ai05-0045-1.txt
!standard 9.2(6) 07-08-06 AI05-0045-1/03
!class binding interpretation 07-04-04
!status work item 07-04-04
!status received 07-04-02
!priority Medium
!difficulty Medium
!qualifier Omission
!subject Termination of unactivated tasks
!summary
Tasks that are created but never activated become terminated when their master is finalized.
!question
9.2(6) says
Should the task that created the new tasks never reach the point
where it would initiate the activations (due to an abort or the raising
of an exception), the newly created tasks become terminated and
are never activated.
A return statement for a type containing tasks that is left without being
completed is going to leave the master of those tasks. But the activation point
(at the site of the function call) could very well still be executed, for instance
if there is a local handler. Moreover, an extended_return can be left by an exit or
goto statement (not one of these cases listed in this sentence), and such exits
will occur mainly in cases where the activation point will in fact be reached. How
can the master being left wait for tasks that have not yet been activated, but are
not terminated because this paragraph does not apply?
!recommendation
(See Summary.)
!wording
Replace 9.2(6) by:
If a return statement is left such that the return object is not returned
to the caller, any task that was created as part or coextension of the
return object immediately becomes terminated and is never activated
[irregardless of where it would have been activated]. Otherwise, if a master
that directly encloses the point where the activation of a task T would be
initiated begins finalization before the activation of T is initiated, T
becomes terminated and is never activated.
AARM Notes: The first case can happen with an exception being raised in a return
statement, by an exit or goto from an extended_return_statement, or by a
return statement being aborted. Any tasks created for the return object of
such a return statement are never activated.
The second case can only happen if the activation point of T is not reached
due to an exception being raised or the task being aborted. Note that this is
exclusive; if the master is being finalized, we're already past the activation
point and since Ada doesn't have any restart semantics, we can never get there.
End AARM Notes.
[Editor's note: The square brackets here mark redundant text.]
!discussion
What is important here is what happens if the master that directly encloses
the activation point of any unactivated tasks is finalized. We don't care why the
tasks were never activated or where they were created. Thus, the original wording
of 9.2(6) says too much. It also has a race condition, in that if the task is
aborted when it reaches the activation point but before the tasks actually are
activated, this rule doesn't seem to apply.
The wording also is not clear about precisely when the tasks become terminated. The tasks
being terminated on creation would meet the requirements of this rule. Thus, the value
of T'Terminated is not well-defined for such tasks. That seems bad.
Because of all of these reasons, we've rewritten the rule in terms of finalization of
a master rather than "reaching the point of activation".
While the most critical point is when the master of the tasks begins to finalize (because
we would need to wait for the unactivated tasks at that point), writing the rule
that way could cause unactivated tasks to hang around in a limbo state for a long time.
(Consider an allocator for an access-to-task that is declared at library level; the
master is that of the environment task.) Thus we talk about the master of that encloses
the activation point of the task.
We need a special rule for a return statement which is completed without returning to the
caller (a "failed" return), because the activation point of those tasks may be executed
if the function executes a second return statement successfully. In that case, the tasks
created by the failed return should never be activated, while the second set created by
the later successful return do need to be activated. This doesn't fit cleanly into
the model.
!corrigendum 9.2(6/2)
Replace the paragraph:
Should the task that created the new tasks never reach the point where it would
initiate the activations (due to an abort or the raising of an exception), the
newly created tasks become terminated and are never activated.
by:
If a return statement is left such that the return object is not returned
to the caller, any task that was created as part or coextension of the
return object immediately becomes terminated and is never activated
irregardless of where it would have been activated. Otherwise, if a master
that directly encloses the point where the activation of a task T would be
initiated begins finalization before the activation of T is initiated, T
becomes terminated and is never activated.
!ACATS test
An ACATS C-Test should be created to check the extended_return case with a transfer of control:
return A : Type_with_a_Task do
...
goto Oops;
...
end return;
<<Oops>>
return B : Type_with_a_Task do
...
end return;
!appendix
From: Stephen W Baird
Date: Monday, April 2, 2007 11:48 AM
9.2(6) says
Should the task that created the new tasks never reach the point
where it would initiate the activations (due to an abort or the raising
of an exception), the newly created tasks become terminated and
are never activated.
The parenthesized part needs updating for Ada05 to handle the case
where an extended return statement for a function whose result type
contains tasks is exited via either an exit statement or a goto statement.
****************************************************************
From: Randy Brukardt
Date: Monday, April 2, 2007 9:40 PM
I'm begining to wonder if this isn't a real hole. Consider something like:
return A : Type_with_a_Task do
...
goto Oops;
...
end return;
<<Oops>>
return B : Type_with_a_Task do
...
end return;
In a case like this, the rule you note doesn't apply, as the "point at which the
tasks are activated" is after the call in the caller of the function. That surely
WILL be executed. But we still don't want the first set of tasks activated. Indeed,
the rule is perfectly OK as originally written, as any case where the activation point
isn't executed will be due to an exception (Program_Error).
So I think we may need a new rule (or a reconsideration of the master rules, or
something), which means a new AI. Sigh.
****************************************************************
From: Stephen W Baird
Date: Tuesday, April 3, 2007 3:55 PM
I don't think there is a new problem here.
The wording of 9.2(6) is terrible, but we've lived with it for years.
Taken literally, the wording is ridiculous: "at the point where something
will never happen, do something", or something like that.
If, strictly speaking, this is defined at all (a point I am not willing to
concede), it certainly requires a crystal ball - do or do not do something now
depending on what is going to happen in the future. This reminds me of the old
definition of the Max_Size_In_Storage_Units attribute ("the maximum value ...
that will be requested ...").
What this wording really means is "at the point where the task activation point
becomes unreachable, terminate the new tasks".
I think this interpretation makes as much sense in Ada05 as it did in Ada95.
In your example, the set of tasks associated with a different extended return
statement (or even a different elaboration of the same extended return statement)
is a different set. Thus, the task activation point for the original set of
tasks is no longer reachable after the goto statement and 9.2(6) does apply.
****************************************************************
From: Randy Brukardt
Date: Monday, April 2, 2007 4:57 PM
> If, strictly speaking, this is defined at all (a point I am not willing to
> concede), it certainly requires a crystal ball - do or do not do something now
> depending on what is going to happen in the future. This reminds me of the old
> definition of the Max_Size_In_Storage_Units attribute ("the maximum value ...
> that will be requested ...").
This is not the way I read this paragraph (with one small exception). It says that
if you never reach the activation point, the tasks are never activated. That doesn't
require a crystal ball. The part about "become terminated" is does have the problem
you are talking about, but it is irrelevant until you reach the task waiting point.
Here, you *are* going to reach the task activation point, but you *still* don't want
to activate the tasks. That is completely different.
> What this wording really means is "at the point where the task activation
> point becomes unreachable, terminate the new tasks".
Maybe. I'm not sure; I always thought it meant "if you reach the task waiting point
(master destruction point), then any unactivated tasks are terminated". Maybe it doesn't
matter in Ada 95 tasks.
> I think this interpretation makes as much sense in Ada05 as it did in Ada95.
For Ada 95, you may be right. But I don't see this for extended returns.
> In your example, the set of tasks associated with a different extended return statement
> (or even a different elaboration of the same extended return statement) is a
> different set.
So what? They're both activated at the same point. And, as a practical matter, they're
going to be on the same ring (list): we're only going to pass one in to take the tasks!
> Thus, the task activation point for the original set of tasks
> is no longer reachable after the goto statement and 9.2(6)does apply.
Huh? It hasn't changed (its the point of the call), and it is surely still going to be
executed.
Anyway, the object is finalized when its (local) master goes away (3.10.2(10.1/2) says that
the master is the return statement until that statement completes). But that makes a
totally new situation: one where the activation point of the tasks is after the master
of the tasks is finalized. Keep in mind that there isn't any wording like 3.10.2(10.1/2)
that applies to the activation point: it never changes its location! So it is initially
outside of the master of the object; then when the return happens, the master changes so
that the activation point is inside as usual.
In an example like this, it's clear that the tasks are gone before the activation point
is reached, but it surely will be reached.
In any case, my concerns and your statement that the original wording
is bad means this is *not* a minor wording change. I don't want that AI derailed because
people are arguing about the meaning of rules, or are just plain confused. So we need an AI.
And given that we're rewording this paragraph anyway, we might as well fix your concern.
I'd dump all of the verbiage and simply say what is important: "if a task has not been
activated when the master on which it depends is waiting for the termination of dependent
tasks, the task becomes terminated and is never activated." This probably wasn't done
because it is a forward reference (and it might make the most sense in 9.3) [although
I note that "terminated" is also a forward reference]. That would drop all of the rot
about why that might happen, which is a certain maintenance problem. If we moved this
to 9.3 (after paragraph 5), then it might make sense to make the current wording a note
in 9.2; then it doesn't have to be exactly right.
****************************************************************
From: Stephen W Baird
Date: Wednesday, April 4, 2007 3:24 PM
This might be ok, but it makes me nervous because it doesn't quite
correspond to how most implementations would want to implement this.
The "typical" implementation that I envision involves terminating these
tasks while still still in the callee. The caller is unaware that these
tasks were ever created and is certainly not responsible for terminating
them. In this example,
function F return R is
begin
return R'(Some_Task_Component => <>,
Some_Natural_Component => Ident_Int (-1),
Some_Other_Task_Component => <>);
exception
when Constraint_Error =>
Do_A_Bunch_Of_Stuff;
return ... ;
end F;
, any tasks that were created as part of the aggregate evaluation before
Constraint_Error was raised would be terminated before, not after,
the call to Do_A_Bunch_Of_Stuff.
According to the wording you suggest, they would be terminated after.
This would be ok if we are really sure that there is no way that anyone
could tell the difference.
I can't think of any way that anyone could tell the difference, but that
is a much weaker statement.
This is my only objection. I like the simplicity of the wording you
propose. Perhaps it could be augmented with an explicit implementation
permission to terminate the tasks earlier; or, at the very least, an
AARM note indicating that this was the intent.
What do you think?
****************************************************************
From: Randy Brukardt
Date: Monday, April 2, 2007 9:40 PM
> This might be ok, but it makes me nervous because it doesn't quite
> correspond to how most implementations would want to implement this.
That's possible; I have no idea how a "typical" implementation works. Indeed,
I don't think such a thing exists; implementations seem to vary widely here.
> The "typical" implementation that I envision involves terminating these
> tasks while still still in the callee. The caller is unaware that these
> tasks were ever created and is certainly not responsible for terminating
> them. In this example,
>
> function F return R is
> begin
> return R'(Some_Task_Component => <>,
> Some_Natural_Component => Ident_Int (-1),
> Some_Other_Task_Component => <>);
> exception
> when Constraint_Error =>
> Do_A_Bunch_Of_Stuff;
> return ... ;
> end F;
>
> , any tasks that were created as part of the aggregate evaluation before
> Constraint_Error was raised would be terminated before, not after,
> the call to Do_A_Bunch_Of_Stuff.
>
> According to the wording you suggest, they would be terminated after.
No, that's wrong. The master of a return statement is the statement itself
(see 3.10.2(10.1/2), which defines the accessibility which then defines the master).
If the return statement is successfully completed, then the master is changed to
that of the caller of the function. It's never the function itself.
So, in this case, when the exception is raised, the return statement is not completed,
and the master that is the return statement is left. That means that the created but
unactivated tasks will be awaited, and that will trigger the new rule and make them
terminated.
Now, I realize that you may not actually implement a "real" master for a return
statement, and simply have the incomplete exit of the return statement finalize and
terminate the tasks. And that should be equivalent. But that's not how RM wording works.
I realize that remembering that return statements are temporary masters is hard to do
(even though it comes from a model that you originated), but it's the rule. Note that
controlled finalization happens here, too, not after the exception handler. (Which is
very ugly for us, but whatever.)
> This would be ok if we are really sure that there is no way that anyone
> could tell the difference.
There is no difference, so I don't think you can tell it. ;-)
> I can't think of any way that anyone could tell the difference, but that
> is a much weaker statement.
True, but I don't see how you could get a different result.
> This is my only objection. I like the simplicity of the wording you
> propose. Perhaps it could be augmented with an explicit implementation
> permission to terminate the tasks earlier; or, at the very least, an
> AARM note indicating that this was the intent.
>
> What do you think?
I don't think you need to terminate the tasks earlier (or can, for that matter), because
the moment when you start awaiting the master is essentially the same moment when you
know that the activation point won't be executed/the return statement will not be
completed.
The only thing that I think might be incompatible is the precise instant when
T'Terminated becomes true. Your reading of the wording seems to make that
retroactive(!) - I think we're better off with the wording clear that isn't ever the case.
So I think you've convinced me that I'm on the right track. It certainly is simpler than
talking about executing the point of task activation. I'll write it up this way and then
the full ARG can tear it to bits...
****************************************************************
From: Randy Brukardt
Date: Wednesday, July 18, 2007 8:02 PM
The wording proposed in Paris for this AI is to replace 9.2(6) with the
following:
If a master includes (directly or indirectly) the creation of a task, and it
begins finalization prior to either initiating the activation of the
new task or returning the task to a caller as part or coextension of
a return object, the new task is never activated and becomes terminated.
However, I don't think this wording works; it has the same problem with the
example Steve gave at the meeting as my original proposal.
Steve's example was:
procedure P is
type Ref is access T_has_Task;
Ptr : Ref;
task Child;
task body Child is
begin
Ptr := new T_has_Task;
end Child;
begin
null;
end P;
The allocator is clearly "indirectly" included in the master of P (as it is
the master of Child). If that master begins finalization during the
execution of the allocator (and before the activation of the task), the task
would be "brutally killed" (as Pascal put it in his notes on the minutes).
But Child is still running and presumably is expecting the task to be
working.
The reason that the "indirectly" was added was so that the outer master that
is handled the return object is also one that "includes the creation of a
task". But this is clearly too broad. I think we need to drop the
"indirectly" and address the case of return objects explicitly.
Something like:
If a master directly includes the creation of a task, and it begins
finalization prior to either initiating the activation of the new task or
returning the task to a caller as part or coextension of a return object,
the new task is never activated and becomes terminated. For the purposes of
this rule, a return object which is returned normally is deemed to have been
directly included in the master surrounding the point of call (see 3.10.2).
I'm not very thrilled with this wording, as it introduces a special case.
OTOH, a return object is the only case where the master of an object changes
after it is created, so it seems likely that it is the only special case
that need be handled.
Tucker *always* has a better idea about wording (even if it is wrong, as in
the wording proposed at the meeting), so I'm hoping that he has a better
idea that actually works. (The rest of you can make suggestions as well, of
course.)
****************************************************************
From: Tucker Taft
Date: Wednesday, July 18, 2007 9:08 PM
I'm not convinced there is a problem, because I don't believe
the execution of a subtask is *included* in the execution of
the parent task. We could be more explicit by clearly limiting
this to masters within the creating task, if we really felt
there was a danger of confusion. Hence:
If a task T1 creates a task T2, and if a master within task T1
that includes (directly or indirectly) the creation of the task T2
begins finalization prior to either initiating
the activation of task T2 or returning task T2 to a
caller as part or coextension of a return object, task T2
is never activated and becomes terminated.
But as indicated above, I think the Paris wording is fine, because
of the use of the phraseology "master includes," which really means
"the execution of the master construct includes." I don't think
we have in the past implied that a task "includes" the execution of its
subtasks.
****************************************************************
From: Randy Brukardt
Date: Wednesday, July 18, 2007 10:00 PM
> If a task T1 creates a task T2, and if a master within task T1
> that includes (directly or indirectly) the creation of the task T2
> begins finalization prior to either initiating
> the activation of task T2 or returning task T2 to a
> caller as part or coextension of a return object, task T2
> is never activated and becomes terminated.
This looks fine to me (and yes, there is a danger, in fact certainty, of
confusion).
> But as indicated above, I think the Paris wording is fine, because
> of the use of the phraseology "master includes," which really means
> "the execution of the master construct includes."
This is news to me. If we meant "the execution of the master construct
includes", we should say so, because otherwise I would expect that what is
"included" in a master to be a statically determined thing (as opposed to
the nesting of masters, which is dynamic). Ada doesn't allow declarations to
occur dynamically!
> I don't think
> we have in the past implied that a task "includes" the execution of its
> subtasks.
But in that case, I have no idea what "indirect" means in this context. If a
master M1 includes the creation of a master M2, then it surely "indirectly"
includes the execution of its subtasks. How could it not? The master created
the task! Indeed, just about the only thing that *could* be indirectly
included would be a subtask, because everything else is directly included
(assuming that masters are transitive, which is something we've generally
assumed elsewhere).
My understanding was that the "(directly or indirectly)" was added to ensure
that transitivity was obvious. But it has exactly the opposite effect,
making it seem likely that unrelated stuff is included. I think an AARM note
explaining precisely what was meant would be more useful, because it can use
as many words as needed to be clear (a parenthetical remark cannot do that).
When it comes to accessibility and masters, *everything* is confusing! We
need to use wording that is absolutely crystal-clear and leaves as little to
the imagination as possible. And I don't want people to have to understand
every nuance of accessibility and masters before they can figure out how
tasks terminate in the usual cases.
****************************************************************
From: Robert A. Duff
Date: Thursday, July 19, 2007 3:03 PM
> > I'm not convinced there is a problem, because I don't believe
> > the execution of a subtask is *included* in the execution of
> > the parent task.
I agree with Tucker about what "included" means (or should mean).
Perhaps the notion of "dynamically enclosing (of one execution by another)"
defined in 11.4(2) would help here? The definition makes it clear that only
one task is involved.
****************************************************************
From: Tucker Taft
Date: Thursday, July 19, 2007 3:25 PM
I don't disagree with your [Randy's - ED] point about being clear,
but it is important to macro-expand the term "master"
to "the execution of a master construct" every place
you see it. A master is an *execution*. That is
pretty well established in 7.6.1(3/2) in the definition
of the term. Of course, informal discussion often
treats "master" as a category of syntactic constructs,
but that of course is wrong... ;-)
****************************************************************
From: Robert I. Eachus
Date: Sunday, July 22, 2007 8:48 PM
> If a task T1 creates a task T2, and if a master within task T1
> that includes (directly or indirectly) the creation of the task T2
> begins finalization prior to either initiating
> the activation of task T2 or returning task T2 to a
> caller as part or coextension of a return object, task T2
> is never activated and becomes terminated.
Hmm. I think the source of the confusion here is mentioning task T1.
We all know that the conditions being discussed can only happen if
there something above the master which becomes terminated. But that has
nothing to do with the conditions addressed. We have an
uninterruptable last wishes situation:
If a master that includes (directly or indirectly) the creation of a
task begins finalization prior to either initiating the activation of
the task or returning the task to a caller as part or coextension of a
return object, the task is never activated and becomes terminated.
So leaving the scope of the master has to change the status of the
created task to terminated. But editing it down like this makes it
clear that it is either redundant or wrong! Currently 9.1(4) says: "For
tasks created by the evaluation of an allocator, the activations are
initiated as the last step of evaluating the allocator, after completing
any initialization for the object created by the allocator, and prior to
returning the new access value." This separation into two steps means
that the case where you have an allocator that creates more than one
task can result in one task becoming activated and immediately aborting
the parent before it can finish activating all the tasks. Worse, what
happens when a created task creates another task as part of its
activation? I could, but, won't, write a test.
If we say instead:
If a master that includes (directly or indirectly) the creation of a
task begins finalization prior to either initiating the activation of
the task or returning the task to a caller as part or coextension of a
return object, the task becomes terminated.
There are no orphans around, and the order of creation of tasks in an
allocator is immaterial. Does this allow, in the nested tasks case, for
a situation where the new task has observable effects during elaboration
of the task body, then fails, taking out the parent? Sure. But I don't
think the intent is to change that. As I see it, all that we really
need to do is insure that tasks don't outlive their master.
****************************************************************
From: Randy Brukardt
Date: Monday, July 23, 2007 11:34 PM
> > If a task T1 creates a task T2, and if a master within task T1
> > that includes (directly or indirectly) the creation of the task T2
> > begins finalization prior to either initiating
> > the activation of task T2 or returning task T2 to a
> > caller as part or coextension of a return object, task T2
> > is never activated and becomes terminated.
>
> Hmm. I think the source of the confusion here is mentioning task T1.
No, that was Tucker's attempt to *fix* the confusion. The problem is whether
"includes indirectly" includes nested tasks. (We don't want it to.) Tucker
claims that it does not, but has no language wording to prove that; I claim
it is unclear and thus we need to fix it.
> We all know that the conditions being discussed can only happen if
> there something above the master which becomes terminated. But that has
> nothing to do with the conditions addressed. We have an
> uninterruptable last wishes situation:
>
> If a master that includes (directly or indirectly) the creation of a
> task begins finalization prior to either initiating the activation of
> the task or returning the task to a caller as part or coextension of a
> return object, the task is never activated and becomes terminated.
This wording has exactly the same meaning as the other one, and it makes the
"includes indirectly" problem much worse, because in the previous wording,
it is clear that the finalization of a master of a task other than the one
that created the task is irrelevant; that is not clear here.
> So leaving the scope of the master has to change the status of the
> created task to terminated. But editing it down like this makes it
> clear that it is either redundant or wrong! Currently 9.1(4) says: "For
> tasks created by the evaluation of an allocator, the activations are
> initiated as the last step of evaluating the allocator, after completing
> any initialization for the object created by the allocator, and prior to
> returning the new access value." This separation into two steps means
> that the case where you have an allocator that creates more than one
> task can result in one task becoming activated and immediately aborting
> the parent before it can finish activating all the tasks.
This is well-defined: aborting a task causes any masters it has to be
finalized. So it is clearly covered by the wording.
> Worse, what happens when a created task creates another task as part of
its
> activation? I could, but, won't, write a test.
What's worse about that? Again, we're only interested in the master of the
task that created the task; and that will always be finalized. The inner
task will be terminated before the outer one, and that is as it should be.
> If we say instead:
>
> If a master that includes (directly or indirectly) the creation of a
> task begins finalization prior to either initiating the activation of
> the task or returning the task to a caller as part or coextension of a
> return object, the task becomes terminated.
All you've done here is eliminate some wording which makes it clear that a
task can't be both terminated and pending actuvation, Essentially, you've
introduced a race condition.
> There are no orphans around, and the order of creation of tasks in an
> allocator is immaterial. Does this allow, in the nested tasks case, for
> a situation where the new task has observable effects during elaboration
> of the task body, then fails, taking out the parent? Sure. But I don't
> think the intent is to change that. As I see it, all that we really
> need to do is insure that tasks don't outlive their master.
There are no orphans in any proposed wording (or the existing wording, for
that matter), since every created task belongs to a master, and all masters
are finalized. So there is no way for an orphan to be created. I think
you're making a problem where none existed.
****************************************************************
From: Robert I. Eachus
Date: Tuesday, July 24, 2007 11:41 AM
>No, that was Tucker's attempt to *fix* the confusion. The problem is whether
>"includes indirectly" includes nested tasks. (We don't want it to.) Tucker
>claims that it does not, but has no language wording to prove that; I claim
>it is unclear and thus we need to fix it.
Ah, I was assuming that you wanted to include created tasks that have
not yet been activated. As I read the intent, tasks that are not yet
activated are affected, and running tasks are only affected if their
master is aborted. (They become abnormal, then must terminate before
the master is completes finalization.) That was the distinction I was
trying to make. In that case, you have to split the hairs I was
splitting about when a task gets activated. My point was that in the
case of record designated by an access value containing more than one
task with say library unit masters, it matters whether all are activated
simultaneously, or if some but not all tasks can be activated before the
abort.
>>If a master that includes (directly or indirectly) the creation of a
>>task begins finalization prior to either initiating the activation of
>>the task or returning the task to a caller as part or coextension of a
>>return object, the task is never activated and becomes terminated.
>
>This wording has exactly the same meaning as the other one, and it makes the
>"includes indirectly" problem much worse, because in the previous wording,
>it is clear that the finalization of a master of a task other than the one
>that created the task is irrelevant; that is not clear here.
Right, it is that never "activated and becomes terminated" that concerns
me. If the master of the tasks is left as part of the termination, no
problem. But if the master of the created tasks is not finalized along
with the task creating the new tasks, the question of indivisibility vs.
some order matters.
>>So leaving the scope of the master has to change the status of the
>>created task to terminated. But editing it down like this makes it
>>clear that it is either redundant or wrong! Currently 9.1(4) says: "For
>>tasks created by the evaluation of an allocator, the activations are
>>initiated as the last step of evaluating the allocator, after completing
>>any initialization for the object created by the allocator, and prior to
>>returning the new access value." This separation into two steps means
>>that the case where you have an allocator that creates more than one
>>task can result in one task becoming activated and immediately aborting
>>the parent before it can finish activating all the tasks.
>
>This is well-defined: aborting a task causes any masters it has to be
>finalized. So it is clearly covered by the wording.
Yes, my wording here probably should have used Tuck's task
designations. Even then it is not clear. Let me try again. Task T1
creates two tasks by means of an allocator. Task T1 is the master, or
includes the master of task T2. Task T3 and possibly more. are also
created by the allocator, but have other masters. For simplicity assume
library level masters.
>>Worse, what happens when a created task creates another task as part
>>of its activation? I could, but, won't, write a test.
>
>What's worse about that? Again, we're only interested in the master of the
>task that created the task; and that will always be finalized. The inner
>task will be terminated before the outer one, and that is as it should be.
Maybe I should write the example. T1 creates T2 and is directly or
indirectly its master. Task T2 as part of its activation creates T5 and
T6 library level masters. T6 raises Tasking_Error as part of, or
immediately subsequent to its activation. If there are no handlers
intervening this will terminate T2 then T1. But what about T5? Can T5
have completed activation before T6? If so since it has a library level
master, it stays alive and can have observable effects. I've always
assumed that 9.2 allowed such tasks to survive termination of their
creator, and that some such tasks and not others might have been
activated, if one of a set of task activations raises Tasking_Error in
its creator. See AARM 9.2(5.a). Tasking_Error is only raised once, even
if several tasks being activated together fail. This is where I get the
"some order" semantics. You don't have to elaborate the
declarative_parts of the various task_bodies sequentially, but if you
don't you have to collect all the Tasking_Errors raised and pass on just
one. See AARM 9.2(5.b). Yes, these are very much not normative text,
but I don't think it is wise to change the implied model.
>>If we say instead:
>>
>>If a master that includes (directly or indirectly) the creation of a
>>task begins finalization prior to either initiating the activation of
>>the task or returning the task to a caller as part or coextension of a
>>return object, the task becomes terminated.
>
>All you've done here is eliminate some wording which makes it clear that a
>task can't be both terminated and pending activation, Essentially, you've
>introduced a race condition.
No, I'm trying to preserve a race condition! As far as any
implemetation is concerned, they can choose not to have tasks in such as
state. A task can become terminated prior to or in the middle of
activation. In particular, if an implementation decides to allow
parallel activation, it is possible for two tasks each to see the other
as pending activation or terminated. The same thing can occur if the
parent or master is aborted--if a task is being elaborated on one
processor while another is causing it to become abnormal, the task whose
task body is being elaborated can be in both states simultaneously.
From an RM perspective, we have a group of tasks being activated
simultaneously from the point of the task doing the activation, but not
from other points of view. In particular if a task creates other tasks
by allocators during activation, they can look at their creator. (Or for
that matter, their siblings, if an object contains several tasks.) They
cannot see the creating task as both callable and terminated, but which
they see in certain programs should be implementation dependent.
>>There are no orphans around, and the order of creation of tasks in an
>>allocator is immaterial. Does this allow, in the nested tasks case, for
>>a situation where the new task has observable effects during elaboration
>>of the task body, then fails, taking out the parent? Sure. But I don't
>>think the intent is to change that. As I see it, all that we really
>>need to do is insure that tasks don't outlive their master.
>
>There are no orphans in any proposed wording (or the existing wording, for
>that matter), since every created task belongs to a master, and all masters
>are finalized. So there is no way for an orphan to be created. I think
>you're making a problem where none existed.
I think we are using slightly different definitions of orphans, but it
doesn't matter in that we agree that there are none. I was thinking of
tasks being activated when their creator, not master, is aborted. Either
the task becomes activated before its creator is terminated, or it dies
before the creator is left. In either case, it is safe for the
implementation to refer to data owned by the creator during activation.
Whether implemetations do that or copy any necessary data to the new TCB
before activation is an implementation detail.
****************************************************************
From: Randy Brukardt
Date: Tuesday, July 24, 2007 1:39 PM
...
> Right, it is that never "activated and becomes terminated" that concerns
> me.
That's Ada 83 wording (9.3(4)) and thus it has always been part of the
model.
> If the master of the tasks is left as part of the termination, no
> problem. But if the master of the created tasks is not finalized along
> with the task creating the new tasks, the question of indivisibility vs.
> some order matters.
Activation is indivisible in the sense that a task either has started
activation or it has not. It's not possible for it to be between those two
states. And, for the purposes of this rule, it is only the start of
activation that matters, not the completion of it.
...
> Maybe I should write the example. T1 creates T2 and is directly or
> indirectly its master. Task T2 as part of its activation creates T5 and
> T6 library level masters.
I assume via allocators, because otherwise the master is the same as it's
parent.
> T6 raises Tasking_Error as part of, or
> immediately subsequent to its activation. If there are no handlers
> intervening this will terminate T2 then T1. But what about T5? Can T5
> have completed activation before T6?
Completed activation is not interesting for the purposes of this rule; it is
only the start that matters.
> If so since it has a library level
> master, it stays alive and can have observable effects.
Yes, of course.
> I've always
> assumed that 9.2 allowed such tasks to survive termination of their
> creator, and that some such tasks and not others might have been
> activated, if one of a set of task activations raises Tasking_Error in
> its creator. See AARM 9.2(5.a). Tasking_Error is only raised once, even
> if several tasks being activated together fail. This is where I get the
> "some order" semantics. You don't have to elaborate the
> declarative_parts of the various task_bodies sequentially, but if you
> don't you have to collect all the Tasking_Errors raised and pass on just
> one. See AARM 9.2(5.b). Yes, these are very much not normative text,
> but I don't think it is wise to change the implied model.
Nothing about this rule changes this model. After all, it only applies when
the master of the tasks is finalized. At that point, it is not possible for
a task to be activated (that always happens within the master that owns the
task).
If task T5 has begun activation, then this rule does not apply to it (it
says "prior to initiating the activation" after all). The other (unchanged)
termination rules will apply to that task. OTOH, if T5 has not begun
activation, then it never can (because the activation point has to have been
aborted/left before the master has started finalization), and this rule
applies. And it says that the task can never be activated and becomes
terminated.
> >>If we say instead:
> >>
> >>If a master that includes (directly or indirectly) the creation of a
> >>task begins finalization prior to either initiating the activation of
> >>the task or returning the task to a caller as part or coextension of a
> >>return object, the task becomes terminated.
> >
> >All you've done here is eliminate some wording which makes it clear that a
> >task can't be both terminated and pending activation, Essentially, you've
> >introduced a race condition.
>
> No, I'm trying to preserve a race condition! As far as any
> implemetation is concerned, they can choose not to have tasks in such as
> state. A task can become terminated prior to or in the middle of
> activation. In particular, if an implementation decides to allow
> parallel activation, it is possible for two tasks each to see the other
> as pending activation or terminated.
No, that isn't possible, because a task can only become terminated after it
has completed all finalization. That can't be "in the middle of activation";
the activation has to have finished (or failed) in order for that to happen.
> The same thing can occur if the
> parent or master is aborted--if a task is being elaborated on one
> processor while another is causing it to become abnormal, the task whose
> task body is being elaborated can be in both states simultaneously.
No, that would be a broken implementation of Ada. The terminated state is
exclusive of all other possible states of a task. You can get to it from
many other states, but it cannot be in any other state.
> From an RM perspective, we have a group of tasks being activated
> simultaneously from the point of the task doing the activation, but not
> from other points of view. In particular if a task creates other tasks
> by allocators during activation, they can look at their creator. (Or for
> that matter, their siblings, if an object contains several tasks.) They
> cannot see the creating task as both callable and terminated, but which
> they see in certain programs should be implementation dependent.
Now you're saying that it can't be in two states at once. Of course, the
exact state you are in will depend on the runtime, the execution speed of
the processor, etc. But it can't be in more than one state.
****************************************************************
From: Randy Brukardt
Date: Saturday, July 24, 2007 12:00 AM
Tucker Taft wrote:
> If a task T1 creates a task T2, and if a master within task T1
> that includes (directly or indirectly) the creation of the task T2
> begins finalization prior to either initiating
> the activation of task T2 or returning task T2 to a
> caller as part or coextension of a return object, task T2
> is never activated and becomes terminated.
This wording has been bothering me for several days based on something
Robert Eachus said,
and I've finally figured out what's the issue is.
I can't convince myself that it must be the case that the same master
encloses the creation and the activation point of a task (even in the
absence of a return statement). There are so many things that create masters
now that it is hard to reason about them, and it is trivial to add some to
declarations (via expressions or Initialize routines).
In any case, I don't think we really need to talk about the creation the
task; that's not very relevant as it is only the activation point that
matters. If we don't do that, then we can eliminate two issues with this
wording: the "indirectly" business, and any concern about which masters are
involved (or not involved).
The wording could be something like:
"If a master that directly encloses the point where the activation of a task
T would be initiated begins finalization before the activation of T is
initiated, T becomes terminated and is never activated."
This wording is a bit redundant (perhaps someone has a idea to improve it),
but it's simpler because we've gotten rid of "indirect"; there is no need to
worry about normal returns here because such a return just changes the
location of activation (and this wording *only* worries about that
location).
We do have one problem though; a return statement which is exited before the
return still might actually execute the activation point - but we don't want
its tasks activated - ever. So we need a special case for that. And we
probably need an AARM note. (Square brackets mean redundant in the wording
proposal below.)
"If a return statement is left such that the return object is not returned
to the caller, any task that was created as part or coextension of the
return object immediately becomes terminated and is never activated
[irregardless of where it would have been activated]. Otherwise, if a master
that directly encloses the point where the activation of a task T would be
initiated begins finalization before the activation of T is initiated, T
becomes terminated and is never activated.
AARM Notes: The first case can happen with an exception being raised in a return
statement, by an exit or goto from an extended_return_statement, or by a
return statement being aborted. Any tasks created for the return object of
such a return statement are never activated.
The second case can only happen if the activation point of T is not reached
due to an exception being raised or the task being aborted. Note that this is
exclusive; if the master is being finalized, we're already past the activation
point and since Ada doesn't have any restart semantics, we can never get there."
I think this is better, although the wording of the first sentence is
somewhat awkward (I'd rather not talk about task creation, but I can't think
of any other way to put it). And it's annoying to put the rare case first.
But there's no dependence on "indirect" masters; everything is defined in
terms of a single, specific master for each task. For that reason, we can't
get in trouble if the definition of masters is tweaked and we don't have to
worry about what master created a task.
Thoughts? Clever ways to simply this wording?
****************************************************************
From: Robert I. Eachus
Date: Wednesday, August 1, 2007 8:14 AM
> This wording has been bothering me for several days based on something
> Robert Eachus said, and I've finally figured out what's the issue is.
I knew we were talking past each other. I think I now know what you
were focusing on.
> I can't convince myself that it must be the case that the same master
> encloses the creation and the activation point of a task (even in the
> absence of a return statement). There are so many things that create masters
> now that it is hard to reason about them, and it is trivial to add some to
> declarations (via expressions or Initialize routines).
I'm convinced that all activations are enclosed in the master, or the
master is at library level. The problem is that you can have nested
activations, and when that happens, specifying the master in a sentence
is tough. From an implementation point of view, it is easy. The master
is deterimined at compile time, by the static nesting of the appropriate
declaration. Of course, that declaration can be a task declaration, an
object declaration, or an access type declaration..
> In any case, I don't think we really need to talk about the creation the
> task; that's not very relevant as it is only the activation point that
> matters. If we don't do that, then we can eliminate two issues with this
> wording: the "indirectly" business, and any concern about which
> masters are
> involved (or not involved).
>
> The wording could be something like:
>
> "If a master that directly encloses the point where the activation of
> a task
> T would be initiated begins finalization before the activation of T is
> initiated, T becomes terminated and is never activated."
Good. This is what I was really trying to explain. I hope you mean what
I was asking for though. Activation is an indivisible operation from
the point of view of the activating task: A group of tasks all get
initiated at a particular point in the execution.
However, that covers a multitude of execution during the elaboration of
the newly created tasks. IF the elaboration of one task causes the
task enclosing it and several others to become completed, is it possible
that some tasks were activated and others never activated? This new
wording seems to avoid discussing that case.
> We do have one problem though; a return statement which is exited before the
> return still might actually execute the activation point - but we don't want
> its tasks activated - ever. So we need a special case for that. And we
> probably need an AARM note. (Square brackets mean redundant in the wording
> proposal below.)
>
> "If a return statement is left such that the return object is not returned
> to the caller, any task that was created as part or coextension of the
> return object immediately becomes terminated and is never activated
> [irregardless of where it would have been activated]. Otherwise, if a master
> that directly encloses the point where the activation of a task T would be
> initiated begins finalization before the activation of T is initiated, T
> becomes terminated and is never activated.
>
I don't think you need the first part of this paragraph given the last
sentence in (Ada 2007) 9.2(4/2):
"For tasks that are part or coextensions of a single object that is not
a stand-alone object, activations are initiated after completing any
initialization of the outermost object enclosing these tasks, prior to
performing any other operation on the outermost object. In particular,
for tasks that are part or coextensions of the object created by the
evaluation of an allocator
<http://www.adaic.org/standards/05rm/html/RM-4-8.html#S0129>, the
activations are initiated as the last step of evaluating the allocator
<http://www.adaic.org/standards/05rm/html/RM-4-8.html#S0129>, prior to
returning the new access value. For tasks that are part or coextensions
of an object that is the result of a function call, the activations are
not initiated until after the function returns."
But the otherwise clause in Randy's proposal again avoids the problem
case I was worrying about. What happens if one of the created tasks
raises an exception during its elaboration? It has already started
activation, and it is possible that several other tasks have as well.
The task raising the exception becomes a failed task, no problem there.
But what about the other tasks which may be initiated at the same point
in the containing task? My read has always been that some tasks may have
(started and) completed elaboration, and such tasks will not wait for
all other tasks in the group to complete activation before executing the
task body. They exist and are active.
Later when one of the tasks in the group fails, these tasks will become
abnormal, then completed etc. It is easy to create a task that takes
say an hour to elaborate before failing. A (legitimate) implementation
might do sequential elaboration in that case. But AFAIK, it is not
required.
Programmers for today's supercomputers want parallel elaboration of
tasks. Forget the programming language used for a minute. Distributing
code to thousands of CPU cores is not easy, and the subject of lots of
study. We don't want to put artificial barriers in their way. My
understanding is that in Ada, the activation point is a synchronization
point for the creator and all the created tasks, but the completion of
activation is a one way syncronization point. When all the created
tasks are activated successfully the creating task can proceed. This
may not seem simple, but in a message passing environment it is about as
simple as possible. The master task sends out messages telling each
processor where in the code to begin execution. If that task gets back
a failure message, it sends out termination messages to all the created
tasks, otherwise when it gets back the right number of successes, it can
continue execution. The one to many messages create the most (wall
clock) overhead, and you want to have just one. If Ada tasks required
an additional synchronization at the end of activation, that would
double the number of messages required as part of program initiation.
More important, it would add a second one to many message.
I wrote another post trying to make what I see more visible. Microsoft
ate it--I left it open overnight without saving a copy, you would have
thought I would have learned that lesson by now. :-( Anyway, I was
still trying to get the explanation right:
When one or more tasks are activated, the activating task, or main
program, sees activation of a set of tasks as an indivisible operation,
which may complete successfully, or raise Tasking_Error.
The activated task however, each has its own view of time, or progress
through the program code. Activation of a task at execution time may
overlap with the activation of other tasks. We had that fight decades
ago. And if no one remembers the problems caused by sequential
activation, don't bother to dig out 1982 (I think) papers. The real
killer was that multiple simultaneous activations could occur in some
cases anyway, and in any case sequential elaboration had its problems.
Eventually, it just seemed silly to make an array of task objects a
special case. (Or a different case from an array of access to task
values. ;-)
****************************************************************
From: Randy Brukardt
Date: Thursday, August 2, 2007 10:12 AM
...
> However, that covers a multitude of execution during the elaboration of
> the newly created tasks. IF the elaboration of one task causes the
> task enclosing it and several others to become completed, is it possible
> that some tasks were activated and others never activated? This new
> wording seems to avoid discussing that case.
That's purely intentional; I don't want this wording to specify something
that was left unspecified in Ada in the past. I certainly think that an
abort of the activator task could leave some of the activating tasks
unactivated, and the wording needs to cover that case without requiring it.
...
> I don't think you need the first part of this paragraph given the last
> sentence in (Ada 2007) 9.2(4/2):
>
> "For tasks that are part or coextensions of a single object that is not
> a stand-alone object, activations are initiated after completing any
> initialization of the outermost object enclosing these tasks, prior to
> performing any other operation on the outermost object. In particular,
> for tasks that are part or coextensions of the object created by the
> evaluation of an allocator
> <http://www.adaic.org/standards/05rm/html/RM-4-8.html#S0129>, the
> activations are initiated as the last step of evaluating the allocator
> <http://www.adaic.org/standards/05rm/html/RM-4-8.html#S0129>, prior to
> returning the new access value. For tasks that are part or coextensions
> of an object that is the result of a function call, the activations are
> not initiated until after the function returns."
This text defines where the activations task place. But in this specific
case (a return statement that is completed (by a transfer of control or an
abort) but that does *not* return to the caller), we *never* want to
activate the tasks, *even* if the activation point is later executed (as it
will be if the transfer of control remains inside of the function and goes
to a point that also executes a return statement).
That's because the second return statement will also create tasks in the
return object, and it is that second set that we want activated, not the
first.
> But the otherwise clause in Randy's proposal again avoids the problem
> case I was worrying about.
I surely hope so; there is no reason to specify anything, and leaving it
unspecified is just fine. What's the problem with that? It's been
unspecified since the beginning of (Ada) time.
In any case, the only thing this wording considers indivisible is the
*start* of activation of a task. Once the activation has started, this
wording does not apply. Period. And this is only relevant for the task
itself, what the activator is doing is mostly irrelevent (which is why I
prefer this wording to Tucker's). The only thing that matters is that the
activator cannot both be starting the activation of a task and finalizing
something at the same time; because finalization is exclusive of other
activities of a task (which necessarily includes activations). In
particular, an aborted task cannot finalize anything until it has stopped
any other activities -- but there is nothing new about that (that's why
"immediate abort" is so hard to define).
****************************************************************
From: Robert I. Eachus
Date: Friday, August 3, 2007 7:05 PM
Totally agree. I originally got torqued out of shape by wording that
involved that implied that activation of a group of tasks was an
indivisible act. Of course, it looks that way from the point of view
of the task doing the activations. It isn't and hasn't been for the
other tasks, and the wording may not have intended to imply that.
****************************************************************
Questions? Ask the ACAA Technical Agent