Currently in the process of trying to resolve an issue around a recursive function call in our code generating a stack overflow and core dump. In my investigation, it appears the converting the recursive function to a procedure fixes our issue. I can easily re-produce via an example to demonstrate a recursive function and procedure failing at different points with different errors.
Recursive function fails at 538 iterations with a core dump, recursive procedure at 19983 with error "SYSTEM ERROR: bfblst -- Too many block levels. (1)"
Starting my client with -s 200000 -nb 2000 as start-up parameters.
So these questions are;
1 - why the different points of failure and different errors
2 - Changing a function to a procedure and vice versa: can there be any side-effects, e.g. record scope etc.
Recursive procedure example;
OUTPUT TO VALUE("c:\temp\recursive-test-proc-output.txt") NO-ECHO. PROCEDURE test: DEFINE INPUT PARAMETER Y AS INT. PUT UNFORMATTED Y SKIP. Y = Y + 1. RUN test(Y). END PROCEDURE. RUN test(0).
Recursive function example;
OUTPUT TO VALUE("c:\temp\recursive-test-func-output.txt") NO-ECHO. FUNCTION test RETURNS LOGICAL (INPUT Y AS INT) : PUT UNFORMATTED Y SKIP. Y = Y + 1. test(Y). END FUNCTION. test(0).
the function returns a value so it needs larger stack frames than the procedure.
Thanks for the reply Gus, which makes sense, but, the order of magnitude is surprising - nearly 4 fold increase in the number of iterations in this simple example.
And, actually if I add a output parameter to my procedure example, the point of failure remains the same.
DEFINE VARIABLE l AS LOGICAL NO-UNDO. OUTPUT TO VALUE("c:\temp\recursive-test-proc-output.txt") NO-ECHO. PROCEDURE test: DEFINE INPUT PARAMETER Y AS INT. DEFINE OUTPUT PARAMETER l AS LOGICAL. PUT UNFORMATTED Y space(5) ETIME SKIP. Y = Y + 1. RUN test(Y, OUTPUT l). END PROCEDURE. RUN test(0, OUTPUT l).
This comes down to the internal implementation of functions vs procedures in the AVM.Functions are pushed into the process stack, and I am guessing your recursive functions blew the process's stack space (different from the AVMs stack space, -s). If you posted the protrace for the recursive function crash, I would guess it shows this. Procedures are not executed the same way, and don't use up so much of the process's stack space. Instead they use the -s space.
Thanks for the explanation Garry. Is the process stack a fixed size and not changeable with parameters?
The protrace when the function fails looks like this;
=====================================================
PROGRESS stack trace as of Wed Oct 05 14:50:06 2016
=====================================================
Startup parameters:
-pf c:\oe102b\startup.pf,-cpinternal ISO8859-1,-cpstream ISO8859-1,-cpcoll Basic,-cpcase Basic,-d dmy,-numsep 44,-numdec 46,(end .pf),-s 200000,-nb 2000
Exception code: C00000FD STACK_OVERFLOW
Fault address: 101FDBE6 01:001FCBE6 C:\oe102b\bin\prow32.dll
Registers:
EAX:027F0EFC
EBX:00000000
ECX:00000000
EDX:00000003
ESI:00000000
EDI:027F0EFC
CS:EIP:0023:101FDBE6
SS:ESP:002B:00092A84 EBP:11953AFC
DS:002B ES:002B FS:0053 GS:002B
Flags:00010206
As Garry implied, this is totally expected behavior. Procedures and functions are not implemented the same way. For Functions, we are looking at a Windows process stack size. The AVM cannot change this.
Forgive me for asking, but why do you have so many levels of recursion. It sounds like this is not a bug on your part, but intended behavior. But it sounds kind of iffy to me!
This is what I expected to see in the protrace.
On 32-bit Windows, I think the stack size is set during the compilation of the AVM executable, OE does not expose a way to reset the process's/thread's stack size at runtime. Depending on the OS, there might be changes you can make (a quick Google suggests ulimit), but I have not taken the time to investigate. You might also get different stack sizes on different platforms.
You are running something prior to 10.2B04. I haven't looked at the build parameters to know if we have increased the stack size in later versions.
Thanks both.
I've picked up another dev's code, not sure why they choose to use recursion for this particular implementation, definitely not essential. The easy refactoring seems to have been to change the function to a procedure and retain the recursion, for the moment.
Running 10.2B07.
Just for the sake of completeness... how things works for OO methods? Is a methods much like a function in that respect, if it’s a void one does it make it equivalent to a procedure or still seen as a function? Any differences between instance methods vs. static ones?