Either I'm confused or the gc is

donallen donaldcallen at gmail.com
Sat Oct 24 14:21:52 UTC 2020


On Friday, 23 October 2020 at 23:41:05 UTC, rikki cattermole 
wrote:
> Thanks!
>
> The problem is not in that block of code I believe.
>
> Now how about bind_text?
>
> Is that still using c variadic args?

No. Read on.

The detective in me couldn't resist (I really need a Peugeot and 
a basset hound) and so I did some more work on this despite not 
really having the time and I have made some progress.

The walk_account_tree function is defined inside the main 
function. Before it is defined,
I prepare all the sql statements needed to do the verification, 
stored in local variables. One of the queries is count_children:

"select count(guid) from accounts where parent_guid = ?1"

That query is run in the section of walk_account_tree that we've 
been looking at:
         // Now do the children of this account
         // First determine how many there are
         bind_text(count_children, 1, account.guid);
         int n_children = one_row(count_children, 
&get_int).integer_value;
         if (n_children > 0)
         {
             Account[] children = new Account[n_children];
             bind_text(find_children, 1, account.guid);

So the result of that query is saved in n_children and determines 
the size of the children array.
Recall that 'Account account' is the first argument to 
walk_account_tree, so n_children is the number of accounts that 
have account.guid,
an account's unique identifier, as its parent. In other words, 
its children.

If there are children, I allocate the children array and run the 
find_children query:

             "select name, guid, ifnull(commodity_guid, ''), flags
                 from accounts where parent_guid = ?1");

The number of rows that come back ought to be same as n_children. 
On a hunch, I put in a
test to check this:

         // Now do the children of this account
         // First determine how many there are
         bind_text(count_children, 1, account.guid);
         int n_children = one_row(count_children, 
&get_int).integer_value;
         if (n_children > 0)
         {
             Account[] children = new Account[n_children];
             bind_text(find_children, 1, account.guid);
             int i = 0;
             while (next_row_available_p(find_children, 
&reset_stmt))
             {
                 children[i].name = 
fromStringz(sqlite3_column_text(find_children, 0)).dup;
                 children[i].path = format("%s:%s", account.path, 
children[i].name);
                children[i].guid = 
fromStringz(sqlite3_column_text(find_children, 1)).dup; // guid
                 children[i].commodity_guid = 
fromStringz(sqlite3_column_text(find_children, 2)).dup; // 
commodity_guid
                 children[i].flags = 
sqlite3_column_int(find_children,
                         3) | account.flags & 
(account_flag_descendents_are_assets
                         | 
account_flag_descendents_are_liabilities | 
account_flag_descendents_are_income
                         | account_flag_descendents_are_expenses | 
account_flag_descendents_are_marketable
                         | 
account_flag_self_and_descendents_are_tax_related
                         | 
account_flag_descendents_need_commodity_link); // flags
                 i = i + 1;
             }
             if (i != n_children)
                 panic(format("walk_account_tree: bad child index, 
%d, %d, %s", i, n_children, account.path));
             foreach (child; children) {
                 walk_account_tree(child, ancestor_flags | 
account.flags);
             }
         }

And, sure enough, the panic happens:
dca at pangloss:~/Software/newcash_d/verifier$ ./verifier 
/tmp/Finances.newcash
walk_account_tree: bad child index, 231, 344, 
:Assets:Investments:Equities and derivatives:Taxable:Donald C. 
Allen 2003 Revocable Trust:TD Ameritrade
dca at pangloss:~/Software/newcash_d/verifier$

Something has caused sqlite to end the return of rows 
prematurely. But next_row_p did not panic,
which it will if the the return code from sqlite3_step, which it 
calls, is other than SQLITE_ROW or SQLITE_DONE.
Since next_row_p just returned 'false', sqlite3_step must have 
returned SQLITE_DONE. sqlite returned 231 of 344 rows but is 
acting like everything is normal.

This explains why my earlier gdb session revealed that the error 
messages the verifier was spewing were due to being fed account 
structs that were all zero.
The recursive calls are being done on all 344 of the elements of 
the children array, but only 231 have data in them.
The rest are zero, as they were when the whole children array was 
allocated and those dataless "children" look bogus to the 
verifier.

Now, I know from prior experiment that if I disable the gc, the 
problem does not occur, so I tried this again with the newly 
installed panic:

dca at pangloss:~/Software/newcash_d/verifier$ ./verifier 
"--DRT-gcopt=disable:1" /tmp/Finances.newcash
dca at pangloss:~/Software/newcash_d/verifier$

This was run with the same executable as the above. No panic.

Next, I put in a call to GC.disable() just before the 'while' and 
a call to GC.enable{} after the loop returns. No panic.

So I moved GC.disable and enable calls into the loop, at first 
with the disable call at the top of the loop, which did not 
panic. I moved it down
one line at a time and when I get to this:

         // Now do the children of this account
         // First determine how many there are
         bind_text(count_children, 1, account.guid);
         int n_children = one_row(count_children, 
&get_int).integer_value;
         if (n_children > 0)
         {
             Account[] children = new Account[n_children];
             bind_text(find_children, 1, account.guid);
             int i = 0;
             while (next_row_available_p(find_children, 
&reset_stmt))
             {
                 children[i].name = 
fromStringz(sqlite3_column_text(find_children, 0)).dup;
                 children[i].path = format("%s:%s", account.path, 
children[i].name);
                 GC.disable();
                children[i].guid = 
fromStringz(sqlite3_column_text(find_children, 1)).dup; // guid
                 children[i].commodity_guid = 
fromStringz(sqlite3_column_text(find_children, 2)).dup; // 
commodity_guid
                 children[i].flags = 
sqlite3_column_int(find_children,
                         3) | account.flags & 
(account_flag_descendents_are_assets
                         | 
account_flag_descendents_are_liabilities | 
account_flag_descendents_are_income
                         | account_flag_descendents_are_expenses | 
account_flag_descendents_are_marketable
                         | 
account_flag_self_and_descendents_are_tax_related
                         | 
account_flag_descendents_need_commodity_link); // flags
                 i = i + 1;
                 GC.enable();
             }
             if (i != n_children)
                 panic(format("walk_account_tree: bad child index, 
%d, %d, %s", i, n_children, account.path));
             foreach (child; children) {
                 walk_account_tree(child, ancestor_flags | 
account.flags);
             }
         }

the panic occurs.

Then I had another of my hunches. I re-wrote the line containing 
the call to format like so:

         // Now do the children of this account
         // First determine how many there are
         bind_text(count_children, 1, account.guid);
         int n_children = one_row(count_children, 
&get_int).integer_value;
         if (n_children > 0)
         {
             Account[] children = new Account[n_children];
             bind_text(find_children, 1, account.guid);
             int i = 0;
             while (next_row_available_p(find_children, 
&reset_stmt))
             {
                 children[i].name = 
fromStringz(sqlite3_column_text(find_children, 0)).dup;
                 size_t n_collections = 
GC.ProfileStats.numCollections;
                 children[i].path = (account.path ~ ":" ~ 
children[i].name);
                 bool collection_occured = 
GC.ProfileStats.numCollections != n_collections;
                children[i].guid = 
fromStringz(sqlite3_column_text(find_children, 1)).dup; // guid
                 children[i].commodity_guid = 
fromStringz(sqlite3_column_text(find_children, 2)).dup; // 
commodity_guid
                 children[i].flags = 
sqlite3_column_int(find_children,
                         3) | account.flags & 
(account_flag_descendents_are_assets
                         | 
account_flag_descendents_are_liabilities | 
account_flag_descendents_are_income
                         | account_flag_descendents_are_expenses | 
account_flag_descendents_are_marketable
                         | 
account_flag_self_and_descendents_are_tax_related
                         | 
account_flag_descendents_need_commodity_link); // flags
                 i = i + 1;
             }
             if (i != n_children)
                 panic(format("walk_account_tree: bad child index, 
%d, %d, %s, %s", i, collection_occurred, n_children, 
account.path));
             foreach (child; children) {
                 walk_account_tree(child, ancestor_flags | 
account.flags);
             }
         }

This does not panic! In theory, this should consume the same 
amount of GC memory as the format call (though I have not looked 
at the format code; there may well
be intermediate allocations as it does its work).

Now I decided to instrument the code so I could determine whether 
GCs were occurring as a result of the call to format:

         // Now do the children of this account
         // First determine how many there are
         bind_text(count_children, 1, account.guid);
         int n_children = one_row(count_children, 
&get_int).integer_value;
         if (n_children > 0)
         {
             size_t collections_before = 0;
             size_t collections_after = 0;
             Account[] children = new Account[n_children];
             bind_text(find_children, 1, account.guid);
             int i = 0;
             while (next_row_available_p(find_children, 
&reset_stmt))
             {
                 children[i].name = 
fromStringz(sqlite3_column_text(find_children, 0)).dup;
                 collections_before += 
GC.profileStats().numCollections;
                 //children[i].path = (account.path ~ ":" ~ 
children[i].name);
                 children[i].path = format("%s:%s", account.path, 
children[i].name);
                 collections_after += 
GC.profileStats().numCollections;
               children[i].guid = 
fromStringz(sqlite3_column_text(find_children, 1)).dup; // guid
                 children[i].commodity_guid = 
fromStringz(sqlite3_column_text(find_children, 2)).dup; // 
commodity_guid
                 children[i].flags = 
sqlite3_column_int(find_children,
                         3) | account.flags & 
(account_flag_descendents_are_assets
                         | 
account_flag_descendents_are_liabilities | 
account_flag_descendents_are_income
                         | account_flag_descendents_are_expenses | 
account_flag_descendents_are_marketable
                         | 
account_flag_self_and_descendents_are_tax_related
                         | 
account_flag_descendents_need_commodity_link); // flags
                 i = i + 1;
             }
             if (i != n_children)
                 panic(format("walk_account_tree: bad child index, 
%d, %d, %s, %d, %d", i, n_children, account.path, 
collections_before, collections_after));
             if (account.path == ":Assets:Investments:Equities and 
derivatives:Taxable:Donald C. Allen 2003 Revocable Trust:TD 
Ameritrade") {
                 writeln(format("walk_account_tree: %d, %d, %s, 
%d, %d", i, n_children, account.path, collections_before, 
collections_after));
             }
             foreach (child; children) {
                 walk_account_tree(child, ancestor_flags | 
account.flags);
             }
         }

Running this produces:
dca at pangloss:~/Software/newcash_d/verifier$ ./verifier 
"--DRT-gcopt=profile:1" /tmp/Finances.newcash
walk_account_tree: bad child index, 231, 344, 
:Assets:Investments:Equities and derivatives:Taxable:Donald C. 
Allen 2003 Revocable Trust:TD Ameritrade, 33, 34
dca at pangloss:~/Software/newcash_d/verifier$

This proves that at least once during the loop that failed, a GC 
occurred as a result of the format call.

Next I tried uncommenting the concatenation line and commenting 
out the format call. That produces:
dca at pangloss:~/Software/newcash_d/verifier$ ./verifier 
"--DRT-gcopt=profile:1" /tmp/Finances.newcash
walk_account_tree: 344, 344, :Assets:Investments:Equities and 
derivatives:Taxable:Donald C. Allen 2003 Revocable Trust:TD 
Ameritrade, 0, 0
         Number of collections:  2
         Total GC prep time:  0 milliseconds
         Total mark time:  0 milliseconds
         Total sweep time:  0 milliseconds
         Max Pause Time:  0 milliseconds
         Grand total GC time:  0 milliseconds
GC summary:    5 MB,    2 GC    0 ms, Pauses    0 ms <    0 ms
dca at pangloss:~/Software/newcash_d/verifier$

So again, using concatenation instead of format fixes the 
problem. And no GCs have occurred after finishing the loop on the 
account at issue.

And one other tidbit. If I add a precision to %s for the path in 
the format call, like so and change the test for the problem 
account by checking for its
guid rather than the truncated path:
         // Now do the children of this account
         // First determine how many there are
         bind_text(count_children, 1, account.guid);
         int n_children = one_row(count_children, 
&get_int).integer_value;
         if (n_children > 0)
         {
             size_t collections_before = 0;
             size_t collections_after = 0;
             Account[] children = new Account[n_children];
             bind_text(find_children, 1, account.guid);
             int i = 0;
             while (next_row_available_p(find_children, 
&reset_stmt))
             {
                 children[i].name = 
fromStringz(sqlite3_column_text(find_children, 0)).dup;
                 collections_before += 
GC.profileStats().numCollections;
                 //children[i].path = (account.path ~ ":" ~ 
children[i].name);
                 children[i].path = format("%s:%.10s", 
account.path, children[i].name);
                 collections_after += 
GC.profileStats().numCollections;
               children[i].guid = 
fromStringz(sqlite3_column_text(find_children, 1)).dup; // guid
                 children[i].commodity_guid = 
fromStringz(sqlite3_column_text(find_children, 2)).dup; // 
commodity_guid
                 children[i].flags = 
sqlite3_column_int(find_children,
                         3) | account.flags & 
(account_flag_descendents_are_assets
                         | 
account_flag_descendents_are_liabilities | 
account_flag_descendents_are_income
                         | account_flag_descendents_are_expenses | 
account_flag_descendents_are_marketable
                         | 
account_flag_self_and_descendents_are_tax_related
                         | 
account_flag_descendents_need_commodity_link); // flags
                 i = i + 1;
             }
             if (i != n_children)
                 panic(format("walk_account_tree: bad child index, 
%d, %d, %s, %d, %d", i, n_children, account.path, 
collections_before, collections_after));
             if (account.guid == 
"c369abf6ec2c7222e3fdd174ce2c0c9a") {
                 writeln(format("walk_account_tree: %d, %d, %s, 
%d, %d", i, n_children, account.path, collections_before, 
collections_after));
             }
             foreach (child; children) {
                 walk_account_tree(child, ancestor_flags | 
account.flags);
             }
         }

I get:
dca at pangloss:~/Software/newcash_d/verifier$ ./verifier 
"--DRT-gcopt=profile:1" /tmp/Finances.newcash
walk_account_tree: 344, 344, :Assets:Investment:Equities 
a:Taxable:Donald C. :TD Ameritr, 0, 0
         Number of collections:  3
         Total GC prep time:  0 milliseconds
         Total mark time:  0 milliseconds
         Total sweep time:  0 milliseconds
         Max Pause Time:  0 milliseconds
         Grand total GC time:  1 milliseconds
GC summary:    5 MB,    3 GC    1 ms, Pauses    1 ms <    0 ms
dca at pangloss:~/Software/newcash_d/verifier$

No panic. So there appears to be a problem in format when dealing 
with strings fed to an unconstrained %s, probably when 
interacting the garbage collector. My guess is that the issue is
in format, not the gc.

That's as far as I've gotten. But I think we know more now than 
before.




More information about the Digitalmars-d mailing list