Either I'm confused or the gc is

donallen donaldcallen at gmail.com
Sun Nov 15 13:43:14 UTC 2020


With the help of an excellent suggestion by Steve Schveighoffer, 
I finally identified this problem yesterday. It was my error, not 
D's.

I will spare you all the details. The problem is in this function:

void bind_text(sqlite3_stmt* stmt, int index, const char[] 
the_text)
{
     if (sqlite3_bind_text(stmt, index, toStringz(the_text), -1, 
null)
!= sqlite_ok)
     {
         stderr.writeln("bind_text failed");
         auto db = sqlite3_db_handle(stmt);
         stderr.writeln("Error: %s", 
fromStringz(sqlite3_errmsg(db)));
         exit(EXIT_FAILURE);
     }
}

which takes a sqlite prepared statement, a query parameter index 
and a D string and attempts to convert the D string to a 
null-terminated C string and call sqlite3_bind_text to bind the C 
string to the indicated query parameter.

The culprit is the call to toStringz. The documentation for that
function give some good advice that I failed to follow:
"Important Note: When passing a char* to a C function, and the C
function keeps it around for any reason, make sure that you keep a
reference to it in your D code. Otherwise, it may become invalid
during a garbage collection cycle and cause a nasty bug when the C
code tries to use it."

That's precisely what is happening here. There is no reference to 
the
string returned by the toStringz call retained and if a GC happens
during a loop retrieving all the rows of a query, the C version 
of the
string that has been passed to sqlite by the sqlite3_bind_text 
call gets
freed by the GC before sqlite is done with it. So now the sqlite 
query
no longer has a proper value for the query parameter, and all 
bets are
off as far as the query behaving correctly. This also explains 
the mystery of why the loop
terminated prematurely when I first discovered this problem.

I fixed it by having bind_text return the C string to its caller, 
to be stored in a variable to protect it from the garbage 
collector. The original error, which was reproducible, is now 
gone. Steve's suggestion, which was to insert frequent calls to 
the garbage collector, provoked a different error reliably that 
is also gone with the fix.

Thanks to everyone who tried to help figure this one out.


More information about the Digitalmars-d mailing list