To save preferences, my app writes a JSON-encoded string S to a file F. Assume that S has N bytes.
Upon attempting to load F at a subsequent launch of the app, it turns out that F contains exactly N NULL bytes.
My guess is that a call to TextOutputStream.Write was non-atomic - it allocated space on disk, but the process crashed before the data could be written to disk.
My attempts to reproduce this behaviour are:
while continuously calling .write in a thread, crash the process in a different thread
while continuously calling .write, force-quit the app via taskmgr.exe
Of the above tests, 1. and 2. succeed in writing a correctly-formed file every time.
It is very important to me to reproduce this issue because diagnostic information from users indicates that it occurs frequently in production versions of the app.
My question is: Under what circumstances does TextOutputStream.write behave in this way?
Footnote: It is in fact possible to reproduce the NULL bytes behaviour exactly by continuously calling .write and pulling out the power cord to cause an abrupt shutdown of the system. But intuitively this feels like too uncommon of a scenario to account for the large number of users that this happens to.
You’re assuming TextOutputStream.Write failed when your only data is that reading the file produced nothing? How did you prove that it wasn’t the read that failed?
Why do I ask this? People frequently have code like
var tis as TextInputStream = TextInputStream.Open(file)
if tis <> nil then
// Only read if tis isn't nil
end
Which leads to problems where if the FolderItem is inaccessible, their read code doesn’t actually read anything.
If the write failed, you’d have an IOException (or an error code in API 1.0) from the Write; but so far your only data is that reading failed. Are you checking for API 1.0 error codes? Are you catching exceptions but not handling them?
How did you prove that it wasn’t the read that failed?
I have seen the result of the issue having occurred in-house just once, but it was days after and I have no idea how it happened.
I also have a selection of samples of this file sent from various users.
In these cases, viewing the file in a hex editor is enough to convince me that the .write failed.
…
Which leads to problems where if the FolderItem is inaccessible, their read code doesn’t actually read anything.
True. But in this case, the file is definitely accessible to .read - it loads in the expected number of bytes. Only problem is all the bytes are 0x00.
If the write failed, you’d have an IOException (or an error code in API 1.0) from the Write
Are the API 1.0 error codes passed through TextOutputStream.LastErrorCode? The app doesn’t check this and probably should - I’ll look into it.
That said, I’d still be very interested to know what kind of external conditions are needed for this issue to actually occur, so that I can be more rigorous in testing any fix.
If you have multiple threads writing to the same file you need to make sure to use a mutex, semaphore or whatever the other is called. Also you can call .flush or .close to write to disk. As .write is not guaranteed to actually write to disk directly.