[Home]

Summary:ASTERISK-11405: Dialplan functions are executed before conditions are evaluated
Reporter:Leif Madsen (lmadsen)Labels:
Date Opened:2008-02-08 11:29:41.000-0600Date Closed:2011-06-07 14:08:09
Priority:MinorRegression?No
Status:Closed/CompleteComponents:Applications/app_exec
Versions:Frequency of
Occurrence
Related
Issues:
Environment:Attachments:
Description:I'm opening this for a bit of discussion as I believe the following conditions could result in inefficiencies in the dialplan.

I have the following bit of dialplan code:

exten => h,n,Verbose(1,DIALSTATUS = ${DIALSTATUS})
exten => h,n,ExecIf($[${DIALSTATUS} = ANSWER],Set,TIME_USED=${MATH(${ANSWEREDTIME}/60,f)})


But when I execute it in the dialplan, I end up with this:

   -- Executing [h@subPlaceCall:2] Verbose("SIP/4918847473-1565e170", "1|DIALSTATUS = CANCEL") in new stack
DIALSTATUS = CANCEL
[Feb  8 12:21:51] WARNING[19032]: func_math.c:169 math: '' is not a valid number
   -- Executing [h@subPlaceCall:3] ExecIf("SIP/4918847473-1565e170", "0|Set|TIME_USED=") in new stack


Which appears to be that the MATH() function is being executed before the condition is checked. That seems backwards to me in that the condition should be checked before any action is taken on the arguments provided.

I've seen the same behaviour with the IF() function as well:

exten => h,n,Exec(${IF(${DIALSTATUS} = ANSWER]?Set(TIME_USED=${MATH(${ANSWEREDTIME}/60,f)}):NoOp())})

Same behaviour happens where MATH() returns an error because ${ANSWEREDTIME} is not populated (and it shouldn't be -- the call was not answered).
Comments:By: Mark Michelson (mmichelson) 2008-02-08 12:26:16.000-0600

I was going to close this as I think the behavior is intentional. I believe the idea is that it is more efficient to substitute all the variables and functions prior to actually running the application. If you set a value for TIME_USED prior to the ExecIf call and check its value after, you'll see that the true branch was not actually executed (i.e. TIME_USED will be the same as what you set it to prior to the ExecIf call), just the variables and functions were substituted.

Now the reason I said I was going to close this was because I thought that since the actual application in the true branch of ExecIf did not get executed it wouldn't be a huge deal. The problem is that this can cause some very unexpected results if for instance you were to use the SET function in the true branch. I suspect that it would get evaluated and set the variable to the new value even if the true application were not executed. In your example, had you done this:

exten => h,n,Verbose(1,DIALSTATUS = ${DIALSTATUS})
exten => h,n,ExecIf($[${DIALSTATUS} = ANSWER],Noop,SET(TIME_USED=${MATH(${ANSWEREDTIME}/60,f)}))

I suspect that TIME_USED would be set to a new value even if the condition were not true. This therefore needs to be looked at more carefully.

By: Leif Madsen (lmadsen) 2008-02-08 12:33:04.000-0600

Yes exactly. The dialplan functions are evaluated before the condition statement is evaluated. This is essentially the same issue that I found, except you replaced MATH() with SET().

i.e. the MATH() function was being executed, but returned an error because the variable was null (${ANSWEREDTIME}), but the variable was not set because the application was not executed (since that is done after the condition is evaluated).

But when you replaced the application with the function, then you were executing the function before the condition was evaluated, and it didn't even matter if the condition was true or not. In fact, you would have executed SET() twice if it were true.



By: Tilghman Lesher (tilghman) 2008-02-11 12:05:05.000-0600

blitzrage: no, you would have only executed the SET once.  putnopvut was initially correct, the values are all evaluated before the application is run.  If you want to conditionally evaluate something, then you need to use two functions, i.e.

NoOp(${SET(TIME_USED=${IF($[${DIALSTATUS} = ANSWER]?$[0${ANSWEREDTIME}/60]:0)})})

The innermost section is evaluated first, followed by each enclosing section in turn, and the final string is passed back to the application to run.

This is an evaluation language, not an interpreted language, so the behavior is to be expected.

By: Leif Madsen (lmadsen) 2008-02-11 12:51:53.000-0600

OK... but in your example, you're not using the MATH() function... which I was using because I needed a floating point number back. But even with your example, since the inner most function is being executed, then MATH() always gets executed,  even when ${ANSWEREDTIME} is NULL.

I'd just use a Goto() to skip over it if ANSWEREDTIME was NULL, but you can't do that in the 'h' extension without changing the CDR record...

By: Tilghman Lesher (tilghman) 2008-02-11 13:00:53.000-0600

Correct, but you can still dispose of the simple error by prefixing the value that might be blank with 0 and get the same result.