Releasing handlers to avoid recursion limits.

I have been battling the recursion limit of Eggplant Purple and have finally come to the reason for this occurance in my code. I am hoping someone out there (Doug?) might be able to understand my structure and maybe give me another option.

The DO command creates another handler that does not get released. Example.

Repeat for number of items in test list
try
  do something
catch 
  LogError (script)
end try


(Script LogError)

LogError
stuff
do ("run" && scriptName &"."& functionName && playerType & "," & loopLocation)

Okay so what I find is by the time to this stage the call stack is around 4 deep. This brings it to up 6. The do which creates a handler to call then calls the handlers. These two never get released from the stack.

I use this do to enter back into the loop to try the test case again before really failing it and also to keep running the rest of the test cases that follow it. So this bring be to my delema. If I have more that a couple of failures in this loop I get locked out when the Recurssion Limit is hit.

What, if anything can be done to improve this?

Thanks,
Charles
Adobe Systemss, inc

Putzing around the code a bit more today and I found a solution for my problem.

Instead of using the “On” handler I revised it to a Function and returned the value of the current loop location. Then on the calling script I do the math there to decrement the loopCounter a value is returned. Using the return closes the handler and free’s up the space.

Lesson learned.

~Charles

Hello Charles, I’m glad to hear you’ve found a way to get this working for you.

What follows is a more complete examination of some of the issues involved and an example script structure that may be helpful to others who would like to do something similar to implement a recovery and retry mechanism for their tests.

There are a couple of things to look at here: the do command, and the way you’re going about repeating the test.

About the Do Command
First, there is rarely a need to use the do command. The main reason one would use it is to execute some code that is constructed by the script at runtime. While do is potentially very powerful and can sometimes achieve things that couldn’t be done any other way, its power also comes at a cost. It calls the SenseTalk compiler at runtime, making it slightly slower to execute, and also (as I think you’ve found) incurring an extra level in the call stack as a result.

Usually, there are other ways to achieve the same result. I’m not sure if what you’ve shown is your actual code (I’m guessing not) but using the do statements you’ve shown as an example, you should be able to get the same results without “do” like this.

Instead of

do something

you could simply say

something

This will work if something is the actual name of the script you want to run. If (as I’m guessing is the case here) something is a variable name containing the name of the script, then you could use the run command, like this:

run something

Your second use of do is more complex:

 do ("run" && scriptName &"."& functionName && playerType & "," & loopLocation)

Here, you are actually constructing a command in text and executing it with do, which is exactly what do is good for. But it’s probably not necessary in this case either.

The run command, as shown in the previous example, will run a script whose name is stored in a variable (and does it without costing you anything extra in call depth). So the first part of the line above could become:

run scriptName

However, since you’re calling a specific handler in the script identified by scriptName, what you’ll really need is:

get scriptName.(functionName)(playerType,loopLocation)

I’ve changed this to use the get command which treats functionName as a function call rather than a command message (the run command currently has some limitations that keep it from providing a similar solution). If the handler for that message is implemented as a “to” handler this will work fine, since a “to” handler will handle either command or function messages. If it was implemented as an “on” handler you’ll have to change it to either a “to” or “function” handler for this solution to work.

By putting functionName in parentheses here, it will be interpreted as an expression (a variable name in this case, although it could also be a more complex expression) that yields the name of the function to call. Without parentheses, it would try to call a function named “functionName” which isn’t what you want. The playerType and loopLocation values are passed as parameters to the function named by functionName.

By switching your code to use run and get instead of do, you can save one level on the call stack in each case.

One Approach to Recovering and Retrying a Failed Test
The second issue to look at is the way you are going about repeating a test after a failure. You can save some frames on your call stack here as well, by letting your LogError handler return and using another repeat loop to try the test again. Here’s what it might look like:

repeat with each item testname in testList
    Log "Running Test " & testname
    repeat 2 times -- maximum number of times to try
        try
            run testname
            Log "Completed Test " & testname
            exit repeat -- test ran successfully, don't repeat again
        catch error
            LogAnError testname, error
        end try
    end repeat
end repeat

to LogAnError testname, error
    LogError "Test " & testname & " got an error: " & error
    -- other important stuff to prepare for running test again
end LogAnError

Here, LogAnError reports the error and performs any necessary cleanup or recovery prior to attempting the test again, but doesn’t re-run the test itself. This way it can return (without adding to the call depth) and allow the calling script to repeat the test. The main test loop makes 2 attempts to run the test (this number could be increased if you like) and stops trying after the test completes successfully.

By making these changes, it should be possible to run any number of tests, repeating each test as many times as needed, without running into the call depth (recursion) limit.

Doug thanks for the verbose description to this post. This helps a great deal in understanding more of how this too works. One question that will help in further design descisions.

If the “to” handler is the same as either the ON or FUNCTION handlers when even have those other two handlers around? I ask because although my fix worked for one aspect of our test cases we have thousands that are still using this Do functionality to try the test case again if it fails. Obviously by discovery and your feedback I will have to change this behavior as well as change the thousand of On handlers to TO handlers.

Thanks,
Charles

I’m not sure exactly what you’re asking here. A “to” handler can be called by either a command or function, whereas “on” handlers are for commands only, and of course “function” handlers are for function calls.

I’m sorry that the solution I offered requires “to” or “function” handlers when your scripts are already written using “on” handlers. Ideally, it should be possible to do the same thing for commands, using the Run command. Unfortunately, the syntax for Run currently doesn’t support some of what was needed, while the function call syntax does, so that is currently the only way to avoid the use of ‘do’ in your more complex case. Possibly a future version will address this, but in the meantime I’m afraid you’ll have to work with what is there.

If this didn’t answer your question, please ask again. :slight_smile:

Beginning with Eggplant 4.0 the syntax of the Run command supports calling a handler whose name is dynamic (comes from a variable or an expression). So the example above could now be changed to:

run scriptName.(commandName) playerType,loopLocation

Here scriptName and commandName are both treated as variables that hold the names of the script and handler to be called. This example will send a “command”-type message that can be handled by either a “to” or “on” handler. The Get command as shown in the earlier example can still be used to send a “function”-type message that can be handled by a “to” or “function” handler.

1 Like

Can this example be included in the documentation? This is powerful stuff to be able to know how to call scripts and handlers dynamically with variables.

I had been using the Do Command because that was the only way I could loop through a list of scripts and handlers and run them dynamically until I came across this little gem of how to get the syntax correct in a Run Command.

I see in the documentation it says -

Calls the scriptName script, or the handlerName handler of that script, as a command. HandlerName is usually just the simple name of a handler without quotes, but may also be given as a quoted literal, or as an expression in parentheses to construct the handler name at run time.

However, it was not clear to me at all on how to write the code in order for it to execute correctly. An example would have made this much more clear.

Thanks!