Preventing Endless Loops

If a loop isn’t written correctly, it can continue running endlessly. If this happens, the only way to stop the loop is to force quit Panorama (sneak peak, another method to prevent this problem will be described below). For example, here is a program that uses a loop to convert every name in the current database database to upper case. The loop starts at the top of the database and works its way to the bottom, which it detects by using the info(“stopped”) function. (By the way, a much better and faster way to perform this task would be to use the formulafill statement, but just play along for the purposes of illustrating an endless loop.)

    firstrecord
    loop
        Name = upper(Name)
        downrecord
    until info("stopped")

But what if we made a mistake, and forgot to include the downrecord statement?

    firstrecord
    loop
        Name = upper(Name)
    until info("stopped")

Now the loop will stay stuck at the top of the database. It will never reach the bottom of the database (unless the database contains only one record), so the loop will never stop – it will run endlessly.

### Aborting an Endless Loop with SHIFT-COMMAND-ESCAPE

If a loop is running endlessly, you can stop it by pressing the SHIFT, COMMAND and ESCAPE keys all at the same time. (Note: This feature was not available in Panorama X 10.1 or earlier.)

This combination was purposefully designed to require two hands, so that you don’t abort a loop accidentally when you don’t mean to. When the abort occurs, it’s as if there was a stop statement in the code – Panorama immediately stops and the rest of the code is ignored.

#### Loops vs. Other Slow Code

The abort feature applies only to loops, including the loop, for, loopwhile, looparray, and loopdataarray statements. It also works with loops constructed with the goto statement.

You cannot abort slow code in general. For example, suppose you have a large database and sorting by state takes 2 minutes. In that situation, you might think that you could stop the sort after 1 minute by pressing SHIFT-COMMAND-ESCAPE.

    field State sortup
    field City sortupwithin

But this doesn’t happen, because the abort only applies to loops. In the code above, there is no chance of an endless loop, because there is no loop at all. The program will finish eventually, no matter what. So the abort key sequence doesn’t apply.

#### Resuming after an Aborted Loop

In the previous section it was stated that when an abort key sequence is detected Panorama immediately stops and the rest of the code is ignored, but that’s actually not quite true. What actually happens is that an error occurs. Normally this stops the procedure, but you can use the try/catch statements to intercept the error and then continue (see Error Handling). This example displays a helpful message and then stops the program, but the code could possibly attempt to continue.

    firstrecord
    let n=1
    try
        loop
            Name = upper(Name)
            downrecord
            n = n+1
        until info("stopped")
    catch
        message "Only "+n+" of "+info("selected")+" records completed!"
        return
    endcatch

If the code inside try/catch could generate other types of errors, you can check info(“error”) to see if `Shift-Command-ESCAPE abort.` is actually the error.

#### What if a loop MUST finish?

In some situations it’s very important that a loop runs until it is finished, no matter how much time it takes. As a programmer, you may want to make sure that a loop isn’t aborted early, leaving a task half finished. In that situation you have two methods you can use to disable the SHIFT-COMMAND-ESCAPE abort key sequence.

The first method is to use the disableabort statement.

    firstrecord
    disableabort
    loop
        Name = upper(Name)
        downrecord
    until info("stopped")
    enableabort

In this case the enableabort statement on the end isn’t really needed, Panorama automaticaly re-enables aborts when the procedure finishes.

Loop aborts aren’t just disabled for the current procedure, they are also disabled for any subroutines called by the code. In this example, a subroutine named DoSomeThings is called. If DoSomeThings has any loops, you won’t be able to abort them. This extends to any additional subroutines called by DoSomeThings

    firstrecord
    disableabort
    loop
        call DoSomeThings
        downrecord
    until info("stopped")
    enableabort

You can also automatically disable aborts for all procedures in a particular database. Simply use the File>Database Options>General panel to enable the Prevent early abort of procedures in this database. option. ProVUE uses this option in all of Panorama’s libraries, because it’s important that the library code not be aborted. (Even when this option is used, you can still use the enableabort statement to temporarily allow loops to be aborted.)

Note that the Prevent early abort of procedures in this database. option applies any time code in that database runs – even if that database is not the currently active database. It will also apply any subroutines called by code in the database.

When you use either of these techniques to disable aborts, you will of course want to be extra careful to make sure that you don’t write a loop that will never end. If you do that, the loop will run endlessly until you force quit Panorama or reboot the entire computer.

### Specifying Maximum Loop Time

In addition to the ability to abort with the SHIFT-COMMAND-ESCAPE key sequence, you can also set up a preference to specify the maximum time that a loop can run. Choose Preferences from the File menu, then click on the Advanced panel. In this panel you can edit the timeout value directly or you can choose from a set of common timeouts from the popup menu. Choose ∞ if you don’t want a timeout, but rather the option to run endlessly forever.

The trick in specifying this value is that you don’t want to make it onerous to wait for if a loop does get locked up, but you don’t want to make the timeout length so short that legitimate loops get cut short because the timer expires before they finish their work.

So now let’s go back to our endless loop example.

    firstrecord
    loop
        Name = upper(Name)
    until info("stopped")

With the timeout preference set to 30 seconds, this loop will run for 30 seconds and then stop with an error, like this.

:

Note: The loop timeout value is ignored if the disableabort statement has been used, or if early abort has been disabled in the Database Options dialog.

#### Customizing the Timeout Delay (TimeLimit Statement)

In addition to setting up an overall timeout for all procedures, you can also set a custom timeout specifically for an individual procedure. This is done with the timelimit statement. For example the statement `timelimit 1200` will set the time limit to 20 minutes, which will allow this loop to continue running even if the database has thousands of records.

    timelimit 1200
    firstrecord
    loop
        Name = upper(Name)
        downrecord
    until info("eof")

Twenty minutes is a long time, and you might not really be sure how long a loop like this will take. In a situation like this, it might be better to simply set the time limit to zero, which completely disables the timeout (essentially setting it to infinity). This insures that the loop will run all the way to the bottom, no matter how many records there are. (Of course in that case you need to double check your code to make sure the loop will really finish eventually.)

    timelimit 0
    firstrecord
    loop
        Name = upper(Name)
        downrecord
    until info("eof")

Note: The timelimit does not apply if the disableabort statement has been used, or if early abort has been disabled in the Database Options dialog.

#### Customizing Timeout Response

If a timeout occurs, it is handled like any other error. This means you can trap the error if you wish (see Error Handling) and handle it in some custom manner, for example displaying a custom alert.

    timelimit 60
    firstrecord
    try
        loop
            Name = upper(Name)
            downrecord
        until info("eof")
    catch
        message "Your request took too long."
    endcatch

#### Loops vs. Other Slow Code

The timeout feature applies only to loops, including the loop, for, loopwhile, looparray, and loopdataarray statements. It also works with loops constructed with the goto statement.

The timeout does not kick in for slow code in general. For example, suppose you have a large database and sorting by state takes 2 minutes. In that situation, you might think that this program would timeout after the second line.

    timelimit 20
    field State sortup
    field City sortupwithin

But this doesn’t happen, because the timeout only applies to loops. In the code above, there is no chance of an endless loop, because there is no loop at all. The program will finish eventually, no matter what. So the timeout limit doesn’t apply in this situation.

See Also

- endloop -- is used at the end of a loop.
- for -- is used at the beginning of a loop that loops a fixed number of times.
- info("timelimitremaining") -- returns the amount of time available (in seconds) before a loop will time out.
- loop -- is used at the beginning of a loop.
- looparray -- is used at the beginning of a loop that loops over the elements of an array.
- loopdataarray -- is used at the beginning of a loop that loops over the elements of a data array.
- loopindex -- allows a procedure to determine how many times a loop has been repeated.
- loopwhile -- is used at the beginning of a loop.
- repeatloopif -- decides whether to continue with a loop or to start over again from the top.
- stoploopif -- decides whether to continue with a loop or to exit the loop immediately.
- timelimit -- specifies the maximum time a procedure can continue to run after this statement.
- until -- is used at the end of a loop, and can control how many times the loop is executed.
- while -- is used at the end of a loop, and can control how many times the loop is executed.

History

Version | Status | Notes
10.2 | New | New in this version.