Our new accounting software reads existing Goldenseal files. It’s a one-time conversion that moves everything into a new file. You’ve spent many hours creating the data, and you might as well keep it.
TurtleSoft has a very big company file, with 35 years of accounting and business records. It takes about 10 minutes to convert it. Sometimes the update finishes OK. Sometimes it crashes at random places near the end.
We haven’t see crashes in smaller test files, so it seemed most likely that it’s a memory problem. However the conversion maxes out at 65 megabytes of RAM use. That’s puny. The debugger output didn’t helped to find a cause. It was rather a mystery.
Last week we converted the most recent TurtleSoft file so we could use it to test the new accounting app. It crashed. Dang. Can’t start testing with the latest data.
We decided to split the conversion process into two parts, to reduce the memory footprint. The first step moves all the records into a new file. That part has never crashed. Then the conversion finishes later, when the Pro file first opens. It adjusts references to bank transactions and breakdowns, and tidies up many other details. Almost everything in the file gets tweaked.
While debugging the two-step process, it gave some new error messages. One was a clue that helped us find the crash source. It was a sneaky one.
Our C++ code has a “watcher” class that manages record lifetimes. When the function finishes, it reduces a reference count. If nothing else needs the record and it is unchanged, the watcher deletes it from RAM. That’s how we conserve memory and prevent leaks.
There’s another watcher that takes records out of the database if their size may increase. Otherwise, they will over-write nearby records and corrupt the file. That watcher restores them at the function end. They may end up in a new location within the file.
The code often needs both types of watchers. In one spot during the conversion, we used both and forgot to control which would clean up first. That created a “race condition”.
On a modern multi-core CPU, the two watchers run at the same time in different threads. Almost always, removing the record from RAM takes longer, so the record is back in the database before it disappears. However, if conditions are just right, the RAM watcher wins the race. If we didn’t tell it to wait, it may delete the record prematurely. When the other watcher tries to clean up, it acts on something that doesn’t exist. That’s 100% guaranteed to crash.
The code probably fails that way just once in 100,000 tries, but that’s about how many times our file runs the race.
Our staff fixed the bug. Then we searched the code base, and found a couple other places with the same error. Oops. Those have been causing rare crashes for years. At least they don’t corrupt any data. We assign records to the database on the fly, but don’t actually write them to disk until things are calm, a few seconds later. So the crash just loses a small chunk of data entry.
The long-term solution was to add a new watcher class that does both things in the proper order. Now the crashes are gone, the code is safer, and our file converts fine. We also went back to the original one-step conversion. It’s easier for users.
Intermittent bugs are the hardest to fix! It sure feels good when one bites the dust. If you ever have a batbug infestation, you’ll feel the same way about them. They’re ancestors to bedbugs, but intermittent, and smarter.
Before building a release version, we need to merge the Qt code into our app so it’s easy to install. That is not an easy process. We’ll probably hire an expert to do it. It also will be time to start paying the Qt Company for a license. Both steps are still at least a month or two off.
However, if you’d like to make sure your Goldenseal company file will transfer properly, we can test it for you now. The sooner we find any other bugs, the better. We won’t look at the data, but at least we can make sure everyone’s files will convert without giving errors. If your file has corrupted records, maybe we can add code to fix them. Contact us at support@turtlesoft.com to arrange a file transfer.
Dennis Kolva
Programming Director
TurtleSoft.com