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