Issue 17499: Conditional node and loop node activations do not wait for contained accept event action activations (fuml-rtf) Source: Model Driven Solutions (Mr. Ed Seidewitz, ed-s(at)modeldriven.com) Nature: Uncategorized Issue Severity: Summary: Specification: Semantics of a Foundational Subset for Executable UML Models (fUML) (formal/2011-02-01) Subclauses: 8.5.3.2.2 ConditionalNodeActivation, 8.5.3.2.3 LoopNodeActivation, 8.5.4.2.3 ExpansionRegionActivation The resolution to Issue 17314 (Structured activity node activations do not wait for contained accept event action activations), on adopted on Ballot 2 of the fUML 1.1 RTF, works for plain structured activity nodes, but it does not work for structured activity nodes that are conditional nodes, loop nodes or expansion regions. For conditional nodes, the resumption of suspension of the node must take place in the context of a specific conditional clause. For loop nodes, resumption must allow continued iteration of the loop, rather than just completion of the node activation. For expansion regions, resumption must take place in the context of a specific expansion activation group. Resolution: Agreed. In order to simplify the resolution, it seems unnecessary to allow accept event actions within the test parts of conditional node clauses or loop nodes. Such tests are supposed to be “side effect free” and waiting for an external event to happen is essentially a non-functional side effect. If accept event actions are not allowed in tests, then, for a conditional node, suspension and resumptiuon can only happen in the body of a selected clause with a true test and, for a loop node, it can only happen in the body of the loop. Revised Text: (Note: The revisions given here presume the revisions given in the resolution to Issue 17314.) In Subclause 7.5.4.1, Figure 7.37, add a note to ReduceAction with the body “An accept event action may not be contained directly or indirectly in the test part of a clause or loop node.” In Subclause 7.5.4.2.1 AcceptEventAction, add the following under “Additional Constraints”: [4] fUML_no_accept_event_action_in_tests An accept event action may not be contained directly or indirectly in the test part of a clause or loop node. self->closure(inStructuredNode.oclAsType(ActivityNode))->forAll(n | let s : StructuredActivityNode = n.inStructuredNode in s->notEmpty() implies (s.oclIsType(ConditionalNode) implies s.clause.test->excludes(n) and s.oclIsType(LoopNode) implies s.test->excludes(n))) In Subclause 8.5.3.1, Figure 8.28: • In the class ConditionalNodeActivation, add the following operations: completeBody() completeAction(): Token[*] resume() • In the class LoopNodeActivation, add the following operations: doLoop(continuing : Boolean) saveBodyOutputs() resume() continueLoop() In Subclause 8.5.3.2.2 ConditionalNodeActivation: • At then end of the operation doStructuredActivity, remove the following statements: OutputPinList resultPins = node.result; OutputPinList bodyOutputPins = selectedClause.bodyOutput; for (int k = 0; k < resultPins.size(); k++) { OutputPin resultPin = resultPins.getValue(k); OutputPin bodyOutputPin = bodyOutputPins.getValue(k); this.putTokens(resultPin, this.getPinValues(bodyOutputPin)); } • Add the following operations: completeBody() // Complete the activation of the body of a conditional note by // copying the outputs of the selected clause (if any) to the output // pins of the node and terminating the activation of all nested nodes. if (this.selectedClause != null) { ConditionalNode node = (ConditionalNode) (this.node); OutputPinList resultPins = node.result; OutputPinList bodyOutputPins = this.selectedClause.bodyOutput; for (int k = 0; k < resultPins.size(); k++) { OutputPin resultPin = resultPins.getValue(k); OutputPin bodyOutputPin = bodyOutputPins.getValue(k); this.putTokens(resultPin, this.getPinValues(bodyOutputPin)); } } this.activationGroup.terminateAll(); completeAction() : Token[0..*] // Only complete the conditional node if it is not suspended. if (!this.isSuspended()) { completeBody(); } return super.completeAction(); resume() // When this conditional node is resumed after being suspended, complete // its body and then resume it as a structured activity node. // [Note that this presumes that accept event actions are not allowed // in the test part of a clause of a conditional node.] completeBody(); super.resume(); In Subclause 8.5.3.2.3 LoopNodeActivation: • At the end of the operation doStructuredActivity, replace the statements from “boolean continuing = true;” to the end of the operation specification with the single statement this.doLoop(true); • At the end of the operation runBody, replace the statements OutputPinList bodyOutputs = loopNode.bodyOutput; ValuesList bodyOutputLists = this.bodyOutputLists; for (int i = 0; i < bodyOutputs.size(); i++) { OutputPin bodyOutput = bodyOutputs.getValue(i); Values bodyOutputList = bodyOutputLists.getValue(i); bodyOutputList.values = this.getPinValues(bodyOutput); } with if (!this.isSuspended()) { this.saveBodyOutputs(); } • Add the following operations: doLoop(in continuing : Boolean) // If isTestedFirst is true, then repeatedly run the test part and the // body part of the loop, copying values from the body outputs to the // loop variables. // If isTestedFirst is false, then repeatedly run the body part and the // test part of the loop, copying values from the body outputs to the // loop variables. LoopNode loopNode = (LoopNode) (this.node); OutputPinList loopVariables = loopNode.loopVariable; OutputPinList resultPins = loopNode.result; while (continuing) { // Set loop variable values this.runLoopVariables(); for (int i = 0; i < loopVariables.size(); i++) { OutputPin loopVariable = loopVariables.getValue(i); Values bodyOutputList = bodyOutputLists.getValue(i); ValueList values = bodyOutputList.values; this.putPinValues(loopVariable, values); ((OutputPinActivation) this.activationGroup .getNodeActivation(loopVariable)).sendUnofferedTokens(); } // Run all the non-executable, non-pin nodes in the conditional // node. ActivityNodeActivationList nodeActivations = this.activationGroup.nodeActivations; ActivityNodeActivationList nonExecutableNodeActivations = new ActivityNodeActivationList(); for (int i = 0; i < nodeActivations.size(); i++) { ActivityNodeActivation nodeActivation = nodeActivations .getValue(i); if (!(nodeActivation.node instanceof ExecutableNode | nodeActivation.node instanceof Pin)) { nonExecutableNodeActivations.addValue(nodeActivation); } } this.activationGroup.run(nonExecutableNodeActivations); // Run the loop if (loopNode.isTestedFirst) { continuing = this.runTest(); if (continuing) { this.runBody(); } } else { this.runBody(); if (this.isRunning() & !this.isSuspended()) { continuing = this.runTest(); } } if (this.isRunning() && !this.isSuspended()) { this.activationGroup.terminateAll(); } else { continuing = false; } Debug.println("[doStructuredActivity] " + (continuing? "Continuing." : this.isSuspended()? "Suspended": "Done.")); } if (this.isRunning() && !this.isSuspended()) { for (int i = 0; i < bodyOutputLists.size(); i++) { Values bodyOutputList = bodyOutputLists.getValue(i); OutputPin resultPin = resultPins.getValue(i); this.putTokens(resultPin, bodyOutputList.values); } } saveBodyOutputs() // Save the body outputs for use in the next iteration. LoopNode loopNode = (LoopNode) this.node; OutputPinList bodyOutputs = loopNode.bodyOutput; ValuesList bodyOutputLists = this.bodyOutputLists; for (int i = 0; i < bodyOutputs.size(); i++) { OutputPin bodyOutput = bodyOutputs.getValue(i); Values bodyOutputList = bodyOutputLists.getValue(i); bodyOutputList.values = this.getPinValues(bodyOutput); } resume() // When this loop node is resumed after being suspended, continue with // its next iteration (if any). Once the loop has completed execution // without being suspended again, complete the action. LoopNode loopNode = (LoopNode) (this.node); this.saveBodyOutputs(); if (loopNode.mustIsolate) { _beginIsolation(); this.continueLoop(); _endIsolation(); } else { this.continueLoop(); } if (this.isSuspended()) { // NOTE: If the subsequent iteration of the loop suspends it again, // then it is necessary to remove the previous suspension from the // containing activity node activation group. this.group.resume(this); } else { super.resume(); } continueLoop() // Continue the loop node when it is resumed after being suspended. If // isTestedFirst is true, then continue executing the loop. If // isTestedFirst is false, then run the test to determine whether // the loop should be continued or completed. // [Note that this presumes that an accept event action is not allowed // in the test part of a loop node.] LoopNode loopNode = (LoopNode) (this.node); boolean continuing = true; if (!loopNode.isTestedFirst) { continuing = this.runTest(); } if (this.isRunning()) { this.activationGroup.terminateAll(); this.doLoop(continuing); } In Subclause 8.5.4.1, Figure 8.29: • In class ExpansionActivationGroup, add the following operations: suspend(activation : ActivityNodeActivation) resume(activation : ActivityNodeActivation) • In class ExpansionRegionActivation, add the attribute: next : Integer [0..1] and the following operations: runIterative() runParallel() doOutput() terminateGroup(activationGroup : ExpansionActivationGroup) isSuspended() : Boolean resume(activationGroup : ExpansionActivationGroup) In Subclause 8.5.4.2.1 ExpansionActivationGroup, add the following operations: suspend(in activation : ActivityNodeActivation) // Suspend the given activation in this activation group. If this is // the only suspended activation, then suspend the associated region // activation. if (!this.isSuspended()) { this.regionActivation.suspend(); } super.suspend(activation); resume(in activation : ActivityNodeActivation) // Resume the given activation in this activation group. If this is the // last suspended activation, then resume the associated region // activation. super.resume(activation); if (!this.isSuspended()) { this.regionActivation.resume(this); } In Subclause 8.5.4.2.3 ExpansionRegionActivation: • Under “Attributes” replace “None” with • next : Integer [0..1] The index of the next activation group to be run, if the expansion region is iterative. • In the doStructuredActivity operation: o In the first branch of the if statement near the end, replace the statements for (int i = 0; i < activationGroups.size(); i++) { ExpansionActivationGroup activationGroup = activationGroups .getValue(i); this.runGroup(activationGroup); } with the statements this.next = 1; this.runIterative(); o In the second branch of the if statement, replace the statements // *** Activate all groups concurrently. *** for (Iterator i = activationGroups.iterator(); i.hasNext();) { ExpansionActivationGroup activationGroup = (ExpansionActivationGroup) i .next(); this.runGroup(activationGroup); } with the single statement this.runParallel(); o Replace the for loop at the end with the single statement this.doOutput(); • In the runGroup operation, replace the following statements (as already modified per the resolution to Issue 17392) if (this.isRunning()) { // Added OutputPinActivationList groupOutputs = activationGroup.groupOutputs; for (int i = 0; i < groupOutputs.size(); i++) { OutputPinActivation groupOutput = groupOutputs.getValue(i); groupOutput.fire(groupOutput.takeOfferedTokens()); } activationGroup.terminateAll(); } with this.terminateGroup(activationGroup); • Add the following operations: runIterative() // Run the body of the region iteratively, either until all activation // groups have run or until the region is suspended. ExpansionActivationGroupList activationGroups = this.activationGroups; while (this.next <= activationGroups.size() & !this.isSuspended()) { ExpansionActivationGroup activationGroup = activationGroups .getValue(this.next-1); this.runGroup(activationGroup); this.next = this.next + 1; } runParallel() // Run the body of the region concurrently. ExpansionActivationGroupList activationGroups = this.activationGroups; // *** Activate all groups concurrently. *** for (Iterator i = activationGroups.iterator(); i.hasNext();) { ExpansionActivationGroup activationGroup = (ExpansionActivationGroup) i .next(); this.runGroup(activationGroup); } doOutput() ExpansionRegion region = (ExpansionRegion) this.node; ExpansionNodeList outputElements = region.outputElement; Debug.println("[doOutput] Expansion region " + region.name + " is " + (this.isSuspended()? "suspended.": "completed.")); if (!this.isSuspended()) { for (int i = 0; i < activationGroups.size(); i++) { ExpansionActivationGroup activationGroup = activationGroups .getValue(i); OutputPinActivationList groupOutputs = activationGroup.groupOutputs; for (int j = 0; j < groupOutputs.size(); j++) { OutputPinActivation groupOutput = groupOutputs.getValue(j); ExpansionNode outputElement = outputElements.getValue(j); this.getExpansionNodeActivation(outputElement).addTokens( groupOutput.takeTokens()); } } } terminateGroup(in activationGroup : ExpansionActivationGroup) if (this.isRunning() & !this.isSuspended()) { OutputPinActivationList groupOutputs = activationGroup.groupOutputs; for (int i = 0; i < groupOutputs.size(); i++) { OutputPinActivation groupOutput = groupOutputs.getValue(i); groupOutput.fire(groupOutput.takeOfferedTokens()); } activationGroup.terminateAll(); } isSuspended() : Boolean // Check if the activation group for this node is suspended. boolean suspended = false; int i = 1; while (i <= this.activationGroups.size() & !suspended) { ActivityNodeActivationGroup group = this.activationGroups.get(i-1); suspended = group.isSuspended(); i = i + 1; } return suspended; resume(in activationGroup : ExpansionActivationGroup) // Resume an expansion region after the suspension of the given // activation group. If the region is iterative, then continue with the // iteration. If the region is parallel, and there are no more suspended // activation groups, then generate the expansion node output. ExpansionRegion region = (ExpansionRegion) this.node; this.resume(); this.terminateGroup(activationGroup); if (region.mode == ExpansionKind.iterative) { this.runIterative(); } this.doOutput(); Actions taken: July 13, 2012: received issue January 7, 2013: closed issue Discussion: End of Annotations:===== m: Ed Seidewitz To: "issues@omg.org" Date: Fri, 13 Jul 2012 15:34:55 -0400 Subject: Conditional node and loop node activations do not wait for contained accept event action activations Thread-Topic: Conditional node and loop node activations do not wait for contained accept event action activations Thread-Index: Ac1hLkyuY2Z38S2mT4+CVYWohKiTgA== Accept-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: acceptlanguage: en-US X-Mailprotector-Decision: deliver X-Mailprotector-Connection: TLSv1|[10.1.50.225]|10.1.50.225|outbound.mailprotector.net|0|0|0|new|ugly|0|0|0|0 X-Mailprotector-Results: null_ptr subject_50_chars subject_10_spaces clean X-Mailprotector-Score: 80 X-Mailprotector-IP-Analysis: 0, 10.1.50.225, Ugly c=0 p=0 Source New X-Mailprotector-Scan-Diagnostics: 0-0-0-6373-c X-Mailprotector-ID: 5d982e51-1cb4-493e-8dd6-9b119c990a97 Specification: Semantics of a Foundational Subset for Executable UML Models (fUML) (formal/2011-02-01) Subclauses: The resolution to Issue 17314 (Structured activity node activations do not wait for contained accept event action activations), on adopted on Ballot 2 of the fUML 1.1 RTF, works for plain structured activity nodes, but it does not work for structured activity nodes that are conditional nodes, loop nodes or expansion regions. For conditional nodes, the resumption of suspension of the node must take place in the context of a specific conditional clause. For loop nodes, resumption must allow continued iteration of the loop, rather than just completion of the node activation. For expansion regions, resumption must take place in the context of a specific expansion activation group. From: Ed Seidewitz To: "issues@omg.org" Date: Fri, 13 Jul 2012 15:38:05 -0400 Subject: Conditional node and loop node activations do not wait for contained accept event action activations [RESEND] Thread-Topic: Conditional node and loop node activations do not wait for contained accept event action activations [RESEND] Thread-Index: Ac1hLqybfU298iYmR7CrBiF8Xd89IA== Accept-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: acceptlanguage: en-US X-Mailprotector-Decision: deliver X-Mailprotector-Connection: TLSv1|[10.1.50.226]|10.1.50.226|outbound.mailprotector.net|-0.998875|0.84258|0|white|ugly|3554|2|0|0 X-Mailprotector-Results: null_ptr subject_50_chars subject_10_spaces clean X-Mailprotector-Score: 80 X-Mailprotector-IP-Analysis: 0, 10.1.50.226, Ugly c=0.84258 p=-0.998875 Source White X-Mailprotector-Scan-Diagnostics: 0-0-0-7720-c X-Mailprotector-ID: db36c12a-da0f-488f-89fa-644940523f03 [Please ignore the previous version of this message. I forgot to list the affected subclauses, which are now included in the version below.] Specification: Semantics of a Foundational Subset for Executable UML Models (fUML) (formal/2011-02-01) Subclauses: 8.5.3.2.2 ConditionalNodeActivation, 8.5.3.2.3 LoopNodeActivation, 8.5.4.2.3 ExpansionRegionActivation The resolution to Issue 17314 (Structured activity node activations do not wait for contained accept event action activations), on adopted on Ballot 2 of the fUML 1.1 RTF, works for plain structured activity nodes, but it does not work for structured activity nodes that are conditional nodes, loop nodes or expansion regions. For conditional nodes, the resumption of suspension of the node must take place in the context of a specific conditional clause. For loop nodes, resumption must allow continued iteration of the loop, rather than just completion of the node activation. For expansion regions, resumption must take place in the context of a specific expansion activation group.