1. Are you using journal or WAL mode?
No, our code has no mention of WAL mode. We do use the Sequelize ORM which recently fixed the way the SQLite files are closed for WAL mode, but we don’t enable WAL mode in the first place when using Sequelize (relevant test to explicitly use WAL mode with Sequelize).
2. Are you making any adjustments to synchronous mode?
No… the only PRAGMAs in our code are PRAGMA key = 'sekrit';
and now PRAGMA integrity_check;
(for UI workaround that clears database in rare case of corruption). I looked through the Sequelize code and don’t see it toying with synchronous mode either.
FWIW I’ve read through How To Corrupt at SQLite File and don’t think we’re doing much creative with SQLite/SQLCipher.
3. Are you using any SQLite extensions, virtual tables, etc?
Our configure command…
./configure --enable-tempstore=yes --enable-load-extension --disable-tcl --with-crypto-lib=commoncrypto\
CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS5"\
LDFLAGS="-framework Security -framework Foundation"
So I think we’re enabling FTS3, FTS5, and JSON but not actually using any of those. Our database is 4 tables and pretty simple…
4. Does your application open and maintain multiple simultaneous connections to the database?
So, I was suspicious about this due to our usage of Sequelize which, due to it’s cross-database nature, has connection pooling logic. But Sequelize does not use any connection pooling logic for SQLite.
Our Electron app launches 2 process: the main process and 1 renderer process. We are careful not to access the database in the main process. We have a check in place to error out if this is ever even attempted:
if (process.type !== 'renderer') {
throw new Error('trying to connect to database in main process');
}
Electron is basically Chrome, so each new “window” (i.e. a Chrome tab) is a separate process. Our app starts with the main process… and then launches a single Electron BrowserWindow (new process via fork()), and that is the only process that connects to SQLite.
5. Are connections accessed across threads?
I’m less sure about this, mainly because my grasp on how threading may work in Chrome/Electron is weak. But this is one similarity between my app and the Signal app… we’re both Electron apps. Signal doesn’t seem to use Sequelize, and I think there’s a low likelihood our use of Sequelize is the issue.
Dumb question #1:
What happens if the app hard crashes during a write? Or power goes out during a write? SQLite should be fine… right? That’s the whole point behind using a hardened database like SQLite/SQLCipher, right?
Dumb question #2:
Do we need to explicitly close the file before a process exits? I’m suspicious of 1.1. Continuing to use a file descriptor after it has been closed. Because I’m at a such a high level in the stack (JavaScript > Node.js > Electron/Chrome) I’m suspicious that somewhere the Node/Electron app quit/crash process could mishandle the file descriptor for the SQLite database. However… it seems that when the Node.js process dies… the file should simply be closed.
We’re still trying to gather data, but for one of the users that saw corruption the pattern was this…
- App was working completely fine at 10:53
- App was suspiciously started (or restarted?) at 11:02
- Corruption found during the app startup at 11:02
The user may have force quit the app and then during restart something went wrong with file descriptors or soft/hard links in the file system?
Will post here if I get any more data.