top of page

Recursive AL

  • adrogin
  • Apr 5
  • 4 min read

Recently I heard an opinion shared by some AL developers that recursion in AL can be dangerous and should be avoided because the AL runtime has a hard cap on the recursion depth and this limitation can easily crash the application. While this is true that the maximum stack depth in AL language is expressed as an arbitrary number of function calls in the stack rather than the memory volume allocated for stack frames as it is done in common general-purpose languages, is it so low that it can be disruptive for recursive functions?

On the one hand, recursion indeed consumes stack and can lead to the notorious stack overflow error, and in most cases recursive code does not lose anything when laid out as a loop (not talking about the functional paradigm here). On the other hand, there are certain cases when the expressiveness of the recursion can make the code more readable and understandable. So should we shy away from recursion in AL, even when it means losing clarity? To answer this question, first of all, we need to understand the extent of the problem: how big or how small is that number limiting the stack depth in AL. Let's run a small test to determine this value.

I will make it very simple - just declare a boundless recursive procedure without the base case and wait for the debugger to break on the exception.

procedure RecursionTest(Level: Integer)
begin
    RecursionTest(Level + 1);
end;

Run this procedure and wait for the result:

RecursionTest(1);

I don't have to wait too long of course, the debugger breaks in a fraction of a second. And this is what it shows.



The number is 997. My guess is that it may actually be 1000, but the counter probably includes a few calls not related to the AL stack. But the exact value is not so important: maximum stack depth just a little short of one thousand is a good enough estimate.

But what if I start adding parameters to the recursive procedure - how will it affect the maximum stack size? C# programs are limited by the amount of memory allocated for the stack, not the number of calls in the stack. So how do these two correlate? Let's push some more parameters to the stack.

Adding ten dummy decimal parameters does not change the maximum recursion depth - it still breaks at level 997.


But 20 decimals in the parameters list make a noticeable difference, hitting the limit at level 717. Twenty is already beyond what is usually considered a reasonable number of function parameters, but for the sake of the test, I continued adding more and went as far as 1500 parameters. Yes, we can do this. Just not in production environments, please. Besides the obvious risk for the mental health of any developer seeing this declaration, every procedure call with the debugger attached becomes agonizingly slow. At his point, I really have to wait for 4-5 minutes until the debugger breaks on the exception after just 9 recursive calls.

We can see that the stack limit in AL is indeed limited to a number of calls in the stack and this number adjusts as we add more parameters to the recursive procedure. But so far, I only added more parameters to the procedure increasing the size of stack frames. What if I strip the procedure of all parameters - will it increase the depth? In Business Central, we always have the database at hand, so why not use it to keep the current stack level and remove all parameters from the procedure definition to reduce the size of the stack frame as much as possible.


I added a table which I called "Stack Test" with one field in it to keep the stack level and write the value from the recursive procedure.

procedure RecursionTest()
var
    StackTest: Record "Stack Test";
begin
    if StackTest.FindLast() then
        StackTest.Level := StackTest.Level + 1
    else
        StackTest.Level := 1;
    StackTest.Insert();
    Commit();

    RecursionTest();
end;

When the exception is hit, the last number saved in the table is still 997. It looks like 997 function calls is as far as we can get in AL, even without any arguments in the stack.


Now the next question: is this number so low that it could become a limiting factor for AL applications? I'd say that 1000 is enough for all practical purposes, unless you need to run heavy computational tasks, like traversing large graphs (which is probably a kind of task better offloaded to an Azure function). Graph theory, for example, finds its application in Business Central's inventory cost adjustment, and the cost traversal is implemented in a few recursive algorithms, but the depth of the call stack in these algorithms rarely exceeds a few dozen calls. It was the cost adjustment in a complicated manufacturing scenario when I hit the stack limit in C/AL for the first time years ago, but that only happened because of a bug in the adjustment algorithm.

And of course it's always a good idea to keep the number of parameters at a reasonable level (for the sake of argument let's define "reasonable" as "no more than 10"), this can save some stack space for a few more recursive calls.

Comments


SIGN UP AND STAY UPDATED!

Thanks for submitting!

  • GitHub
  • Grey LinkedIn Icon
  • Twitter

© 2021 Developer's thoughts about Microsoft Business Central.  Proudly created with Wix.com

bottom of page