!standard 9.2(6) 08-04-21 AI05-0045-1/05 !class binding interpretation 07-04-04 !status WG9 Approved 08-06-20 !status ARG Approved 9-0-0 06-11-10 !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 the master that directly encloses the point where the activation of a task T would be initiated, completes before the activation of T is initiated, T becomes terminated and is never activated. Furthermore, if a return statement is left such that the return object is not returned to the caller, any task that was created as a part of the return object or one of its coextensions immediately becomes terminated and is never activated. AARM Notes: The first case can only happen if the activation point of T is not reached due to an exception being raised or a task or statement being aborted. Note that this is exclusive; if the master is being finalized, we're already past the activation point. The second 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. End AARM Notes. !discussion What is important here is what happens if the master that directly encloses the activation point of any unactivated tasks is completed. 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 completion 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 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) @drepl 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. @dby If the master that directly encloses the point where the activation of a task @i would be initiated, completes before the activation of @i is initiated, @i becomes terminated and is never activated. Furthermore, if a return statement is left such that the return object is not returned to the caller, any task that was created as a part of the return object or one of its coextensions immediately 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; <> 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; <> 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 , the activations are initiated as the last step of evaluating the allocator , 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 > , the > activations are initiated as the last step of evaluating the allocator > , 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. ****************************************************************