Collecting garbage at the wrong time
In this post: Heartbeat: Garbage collection in VFP and .NET are similar, I talked about how the VFP name table is garbage collected.
Here’s a bug that’s been in the product since forever. It involves having a name table overflow and garbage collection occurring at an unexpected time.
Run the code below. It first calls a routine to create many random names to almost fill the name table. Then it tries to execute code in a PRG that contains many PROCs.
If the NUM is large enough, the name table will overflow, causing a name table garbage collection to occur. If the gc occurs while loading the compiled PRG object code, the partially loaded names are marked as garbage and discarded from the name table, causing seemingly random behavior, such as procedure not found or execution of the wrong procedure.
What other random behavior do you get when you run this code?
The correct behavior should be:
1. if UseNames makes the vars LOCALs, then the gc can successfully free the names and make enough room in the name table, and the test procs run correctly, with VAL and Procno changing correctly. The exact expected behavior can be seen by commenting out both the LOCAL and PUBLIC lines in UseNames
2. if UseNames makes the vars PUBLIC an error message indicating there are too many names used in the name table.
Here are some reasons why this bug is rarely encountered:
It requires an application to use lots of names to fill the name table. Most applications have a fixed number of variables, fields, object, table names and aren’t generating new ones in a loop
It requires the garbage collection to occur at a particular place (loading a compiled file which adds enough new names to the name table)
To allow many variables and execution stack, make sure your config.fpw has the lines:
mvcount=65000
stacksize=65000
UseNames(1,63000) && Make name table very full
?"Done filling name table"
MakeTest("TestA",1500,1)
DO testA
PROCEDURE UseNames(base,n)
LOCAL i
FOR i = 1 TO n
cvar="x"+PADL(base,3,"0")+TRANSFORM(i) && create a unique var name like "x0011"
LOCAL (cVar) && create the var as LOCAL
* PUBLIC (cVar) && create the var as public
ENDFOR
RETURN && Local vars get released when they go out of scope
PROCEDURE MakeTest(cTestName,nCnt,nMode)
SET TEXTMERGE off
SET TEXTMERGE to
ERASE (cTestName+".prg")
SET TEXTMERGE ON TO (cTestName+".prg") noshow
\clear
\PUBLIC val,Procno
\val="start"
\?<<nCnt>>
\Procno = 0
\DO <<cTestName>>1
\?"val = ",val
FOR i = 1 TO nCnt
\ PROCEDURE <<cTestName>><<i>>
\ Procno=Procno+1
\ IF VAL(SUBSTR(PROGRAM(),6)) != Procno
\ ?"***bad***",PROGRAM(),Procno
\ suspend
\ ENDIF
IF i < 100
\ val=val+" "+ program()
ENDIF
IF nMode=1
\ do <<cTestName>><<i+1>>
ENDIF
ENDFOR
\ PROCEDURE <<cTestName>><<nCnt+1>>
\ val = val+"Jelly Bean "+TRANSFORM(<<nCnt>>)
SET TEXTMERGE off
SET TEXTMERGE to
MODIFY COMMAND (cTestName) NOWAIT
COMPILE (cTestName)
RETURN
Comments
- Anonymous
June 16, 2006
A customer asked
1) Is it better to set it to 0 (Auto) or some "larger" number, such as -16 (1024kb).&nbsp;... - Anonymous
April 11, 2008
Writing programs using .Net is very productive. One reason is because much of memory management is “managed”