Fix #1571: collection freezes while another QElectroTech is open

Every QElectroTech on a computer shares elements_cache.sqlite. The
cache set PRAGMA locking_mode = EXCLUSIVE, so a QElectroTech that had
read the file once kept it locked until it closed. A second one -- a
build in another folder, or a build beside a released version, which
SingleApplication lets run because its key includes the program's path
and version -- could not write to it: each write waited SQLite's 5 s
busy timeout and failed, on the GUI thread, twice for every symbol the
collection showed. The report's log is exactly that: an empty
QSqlError every 5.0 s for three minutes.

Use SQLite's normal locking, which holds a lock only while a statement
runs, and give this connection no busy timeout: the cache only saves
time, so a busy file is a cache miss, never a wait. Print the failing
query's error, not the connection's, which was always empty.

Measured with the real program running and a second process writing to
its cache: before, the write fails "database is locked" after 5.01 s;
after, it succeeds in 0.02 s. tst_elementcachelock opens two caches on
one file; it fails before the fix with the report's empty QSqlError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-21 22:06:01 +13:00
parent 8ce3b214c1
commit 2bedfaa394
3 changed files with 60 additions and 3 deletions
+7 -3
View File
@@ -40,6 +40,11 @@ ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, Q
QString connection_name = QString("ElementsCollectionCache-%1").arg(cache_instances++);
cache_db_ = QSqlDatabase::addDatabase("QSQLITE", connection_name);
cache_db_.setDatabaseName(database_path);
// Every QElectroTech on this computer shares this file: two builds in
// different folders, or a build beside a released version, both run.
// The cache only saves time, so never wait for another one to let go
// of it -- a busy file is a cache miss (#1571).
cache_db_.setConnectOptions("QSQLITE_BUSY_TIMEOUT=0");
if (!cache_db_.open())
qDebug() << "Unable to open the SQLite database " << database_path << " as " << connection_name << ": " << cache_db_.lastError();
@@ -49,7 +54,6 @@ ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, Q
QSqlQuery(cache_db_).exec("PRAGMA journal_mode = MEMORY");
QSqlQuery(cache_db_).exec("PRAGMA page_size = 4096");
QSqlQuery(cache_db_).exec("PRAGMA cache_size = 16384");
QSqlQuery(cache_db_).exec("PRAGMA locking_mode = EXCLUSIVE");
QSqlQuery(cache_db_).exec("PRAGMA synchronous = OFF");
// Previews used to be stored on an opaque white sheet; they are
@@ -301,7 +305,7 @@ bool ElementsCollectionCache::cacheName(const QString &path,
insert_name_ -> bindValue(":name", current_name_);
if (!insert_name_ -> exec())
{
qDebug() << cache_db_.lastError();
qDebug() << insert_name_ -> lastError();
return(false);
}
return(true);
@@ -327,7 +331,7 @@ bool ElementsCollectionCache::cachePixmap(const QString &path,
insert_pixmap_ -> bindValue(":pixmap", QVariant(ba));
if (!insert_pixmap_->exec())
{
qDebug() << cache_db_.lastError();
qDebug() << insert_pixmap_ -> lastError();
return(false);
}
return(true);