Why use persistent procedures?

As you have seen, you can run one procedure from another. When you do this, you can pass INPUT parameters that are used by the procedure you run. When that procedure gets to the end of its ABL code, it terminates and returns any OUTPUT parameters to the caller. This works the same for both external procedures (independent procedure source files) and for the internal procedures inside them.

These procedures form a call stack. On top of the stack is the main procedure you run from the editor or from the OpenEdge startup command line. Below this is another procedure the main procedure runs, which in turn can run another procedure that is added to the stack, and so on. As each procedure terminates, it returns control to the procedure above it and is removed from the stack. All the resources it uses are freed up, and the procedure effectively disappears from the session unless it is run again. This figure provides a rough sketch of how the procedure call stack can look.

Figure 1. A procedure call stack
In this example, MainProc.p runs SubProc.p. SubProc.p runs an internal procedure (InternalProc) inside it. While the ABL interpreter is executing code in InternalProc, the call stack looks like this:
MainProc.p
SubProc.p
InternalProc

What does this really mean? All three of these procedures have been instantiated and are currently in the session’s memory. What is actually in memory is the r-code for the procedure, which the interpreter understands, whether this was a .r file you previously compiled and saved or whether the ABL Virtual Machine (AVM) has to compile the procedure on the fly at runtime from its source code.

It also means that all the elements that each procedure defines are currently in scope. Any variables, buffers, and other procedure objects have been instantiated along with the executable r-code. The AVM has allocated the memory and other resources needed to execute them.

It also means that control of the application flow is fairly rigid. The code in MainProc.p cannot do anything at all while SubProc.p is running. Likewise, the code in the main block of SubProc.p cannot do anything at all while InternalProc is running. The three procedures together form a single thread that cannot be interrupted except to cancel the whole program execution.

When InternalProc returns to the rest of SubProc.p, any resources it defined, such as local variables, are freed up. SubProc.p can go on with the rest of its program logic. When SubProc.p returns to MainProc.p, all the resources allocated to it, including the in-memory copy of its executable code, are released and disappear. The code in MainProc.p has no way of accessing anything in SubProc.p except by receiving it back in the form of an OUTPUT parameter, because while SubProc.p is running, MainProc.p is completely blocked. When SubProc.p returns control to MainProc.p, it completely disappears.

The result is a very hierarchical application, both in its physical construction and in its user-visible execution. In a more modern application, designed to be distributed between client user interface sessions and separate server sessions that control the database, with the need to communicate with a variety of devices and other applications and to make the user much more in control of the application flow than before, this programming style is not sufficient. The procedures in your application must have greater flexibility in how they interact with each other, without depending on a rigid hierarchy of execution. Persistent procedures provide this flexibility.