From bf4d3353ae3fed31186295c38d3dc309f7fc94e8 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 19 Jun 2026 08:34:02 +1200 Subject: [PATCH] Fix #492: wait for async backup before destroying QETProject writeBackup() fires QtConcurrent::run(QET::writeToFile, ..., &m_backup_file) fire-and-forget: the QFuture was discarded and nothing kept m_backup_file alive until the worker finished. If the QETProject was destroyed first, the worker wrote through the freed member -> use-after-free crash in QET::writeToFile (intermittent; ~1/6 on short-lived CLI runs). Store the QFuture and waitForFinished() in ~QETProject (and before setFilePath() re-points the managed backup file). Also skip launching a new backup while one is still running, so two threads never write m_backup_file at once. The Qt6 path is still a TODO stub and the QtConcurrent block is KF5-only, so this affects only the Qt5/KF5 build that actually has the backup code. --- sources/qetproject.cpp | 13 ++++++++++++- sources/qetproject.h | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 9fafa710c..21dedd6dc 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -137,6 +137,11 @@ QETProject::QETProject(KAutoSaveFile *backup, QObject *parent) : */ QETProject::~QETProject() { + //Wait for any in-flight async crash-recovery backup to finish: the worker + //writes through &m_backup_file, a member that would otherwise be destroyed + //under it (issue #492). + m_backup_future.waitForFinished(); + //We block database signal to avoid hundreds of unnecessary emitted signal //due to deletion (diagram, item, etc...) and as much update made in the not yet deleted things. m_data_base.blockSignals(true); @@ -339,6 +344,8 @@ void QETProject::setFilePath(const QString &filepath) } #ifdef BUILD_WITHOUT_KF5 #else + //Don't close/re-point the backup file while a backup is still writing it. + m_backup_future.waitForFinished(); if (m_backup_file.isOpen()) { m_backup_file.close(); } @@ -1809,8 +1816,12 @@ void QETProject::writeBackup() #ifdef BUILD_WITHOUT_KF5 #else # if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove + //Don't launch a new backup while the previous one is still writing: + //both would write through &m_backup_file on different threads. + if (m_backup_future.isRunning()) + return; QDomDocument xml_project(toXml()); - QtConcurrent::run( + m_backup_future = QtConcurrent::run( QET::writeToFile,xml_project,&m_backup_file,nullptr); # else # if TODO_LIST diff --git a/sources/qetproject.h b/sources/qetproject.h index d78232de5..0ed828953 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -35,6 +35,7 @@ #endif #include +#include class Diagram; class ElementsLocation; @@ -295,6 +296,7 @@ class QETProject : public QObject bool m_freeze_new_conductors = false; QTimer m_save_backup_timer, m_autosave_timer; + QFuture m_backup_future; #ifdef BUILD_WITHOUT_KF5 #else KAutoSaveFile m_backup_file;