[Home]

Summary:ASTERISK-25525: A call EndWhile can cause the dial plan to jump to another context.
Reporter:Steven Wheeler (swheeler)Labels:
Date Opened:2015-11-05 16:44:49.000-0600Date Closed:
Priority:MinorRegression?
Status:Open/NewComponents:Applications/app_while Documentation
Versions:11.6.0 13.18.4 Frequency of
Occurrence
Constant
Related
Issues:
Environment:Attachments:
Description:[Edit by Rusty - Adding a note here at the top for quick reference. The documentation is not sufficient currently. See Richard's comment]

I would expect that a While/EndWhile loop would be restricted to a single context. However, in some cases a call EndWhile can cause the dial plan to jump to another context. We have noticed this with Gosub and Goto.

Here is a simple example:
{code:title=extensions.conf|borderStyle=solid}
[test-while-out]
exten => s,1,Set(i=0)
same => n,While($[${INC(i)} <= 5])
 same => n,Log(NOTICE,i is ${i})
 same => n,Gosub(test-while-inner,s,1)
same => n,EndWhile

[test-while-inner]
exten => s,1,Set(j=0)
same => n,While($[${INC(j)} <= 5])
 same => n,Log(NOTICE,j is ${j})
 same => n,Return
same => n,EndWhile
same => n,Return
{code}

And in the Asterisk console execute:
{code:title=Console|borderStyle=solid}
*CLI> originate Local/s@test-while-out application wait 10
   -- Executing [s@test-while-out:1] Set("Local/s@test-while-out-00000008;2", "i=0") in new stack
   -- Executing [s@test-while-out:2] While("Local/s@test-while-out-00000008;2", "1") in new stack
   -- Executing [s@test-while-out:3] Log("Local/s@test-while-out-00000008;2", "NOTICE,i is 1") in new stack
[2015-11-05 16:28:59] NOTICE[27770][C-00000013]: Ext. s:3 @ test-while-out: i is 1
   -- Executing [s@test-while-out:4] Gosub("Local/s@test-while-out-00000008;2", "test-while-inner,s,1") in new stack
   -- Executing [s@test-while-inner:1] Set("Local/s@test-while-out-00000008;2", "j=0") in new stack
   -- Executing [s@test-while-inner:2] While("Local/s@test-while-out-00000008;2", "1") in new stack
   -- Executing [s@test-while-inner:3] Log("Local/s@test-while-out-00000008;2", "NOTICE,j is 1") in new stack
[2015-11-05 16:28:59] NOTICE[27770][C-00000013]: Ext. s:3 @ test-while-inner: j is 1
   -- Executing [s@test-while-inner:4] Return("Local/s@test-while-out-00000008;2", "") in new stack
   -- Executing [s@test-while-out:5] EndWhile("Local/s@test-while-out-00000008;2", "") in new stack
   -- Executing [s@test-while-inner:2] While("Local/s@test-while-out-00000008;2", "1") in new stack
   -- Executing [s@test-while-inner:3] Log("Local/s@test-while-out-00000008;2", "NOTICE,j is 2") in new stack
[2015-11-05 16:28:59] NOTICE[27770][C-00000013]: Ext. s:3 @ test-while-inner: j is 2
   -- Executing [s@test-while-inner:4] Return("Local/s@test-while-out-00000008;2", "") in new stack
[2015-11-05 16:28:59] ERROR[27770][C-00000013]: app_stack.c:378 return_exec: Return without Gosub: stack is empty
 == Spawn extension (test-while-inner, s, 4) exited non-zero on 'Local/s@test-while-out-00000008;2'
{code}

The call starts at test-while-out which contains a While/EndWhile loop. Inside that loop it logs the value of {{i}} and the Gosubs to test-while-inner. The inner context also contains a While/EndWhile loop but we return from inside the loop. In the real world this would be due to some conditional statement. After returning to the outer loop we call EndWhile. I would expect this to bring us back to the beginning of the outer loop. But it actually brings us back to the beginning of the inner loop.
Comments:By: Asterisk Team (asteriskteam) 2015-11-05 16:44:50.945-0600

Thanks for creating a report! The issue has entered the triage process. That means the issue will wait in this status until a Bug Marshal has an opportunity to review the issue. Once the issue has been reviewed you will receive comments regarding the next steps towards resolution.

A good first step is for you to review the [Asterisk Issue Guidelines|https://wiki.asterisk.org/wiki/display/AST/Asterisk+Issue+Guidelines] if you haven't already. The guidelines detail what is expected from an Asterisk issue report.

Then, if you are submitting a patch, please review the [Patch Contribution Process|https://wiki.asterisk.org/wiki/display/AST/Patch+Contribution+Process].

By: Richard Mudgett (rmudgett) 2015-11-05 17:52:51.872-0600

Your dialplan is incorrect.  You never exited the inner while loop.  That is why it returns to the inner while loop's location.  You need to use ExitWhile to terminate the loop early before you can return from the subroutine.  Also you have to be careful about the use of ExitWhile because the loop has to have seen the loop's EndWhile for ExitWhile to know where execution needs to jump to the end of the loop.

Usage rules:
# {{While}} marks the start of a while loop in the dialplan as well as the condition to keep executing the loop.  For the {{While}} to terminate the loop, the loop's {{EndWhile}} must have been executed before.  This implies that the while condition must have been true at least once.
# {{EndWhile}} marks the end of the while loop.
# {{ExitWhile}} breaks out of the while loop and jumps to the loop's {{EndWhile}} location.  For {{ExitWhile}} to know where to go, the loop's {{EndWhile}} must have been executed before.
# {{ContinueWhile}} jumps to the current while loop's {{While}} location.

[~rnewton] This information needs to be added to the {{While}} application's documentation as it is currently lacking much usage detail.

By: Steven Wheeler (swheeler) 2015-11-06 09:18:08.581-0600

These conditions severely limit the usefulness of while loops and significantly add to the complexity of using them. If I want to return from inside a loop that means I need to set a variable, check the variable, exit the loop, then check that variable again to see if I return or continue execution.

{code}
[my-subroutine]
exten => s,1,Set(i=0)
same => n,While($[${INC(i)} <= 5])
same => n,Set(return=$[some condition])
same => n,ExecIf($[${return}]?ExitWhile)
same => n,Noop; More loop logic ...
same => n,EndWhile
same => n,ExecIf($[${return}]?Return)
same => n,Noop; More subroutine logic ...
same => n,Return
{code}

This code is far less readable and takes it much longer to understand what is happening. Also, I am unclear on whether or not this would work if I wanted to exit the loop on the first iteration?

It would make a lot more sense for any application that changes the dialplan's execution stack to have the effect of ending the while loop. Or to have some loop identifier so that you can specify which While an EndWhile will return to.