From 19e99aab021f568f41cb3e50385fed160d3630d3 Mon Sep 17 00:00:00 2001 From: Shane Ringrose Date: Fri, 12 Jun 2026 05:25:44 +1200 Subject: [PATCH] CLI: disable async crash-recovery backup in headless mode (fixes segfault) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QETProject schedules an asynchronous crash-recovery backup on construction (writeBackup() -> QtConcurrent::run(QET::writeToFile, ..., &m_backup_file)). In one-shot CLI mode the QETProject is destroyed as soon as the command returns, while that background write still references its m_backup_file member — an intermittent use-after-free segfault during teardown (~1 in 6 runs; observed on --resave and --set-titleblock). A crash-recovery backup is meaningless for a short-lived headless command, so add QETProject::setBackupEnabled(false), called from the CLI entry in main(). writeBackup() then early-returns, so no background write is ever launched. Fixes the crash for all CLI commands. See #492. --- sources/main.cpp | 5 +++++ sources/qetproject.cpp | 9 +++++++++ sources/qetproject.h | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/sources/main.cpp b/sources/main.cpp index 89e0eaf1c..8f54279b5 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -19,6 +19,7 @@ #include "machine_info.h" #include "qet.h" #include "qetapp.h" +#include "qetproject.h" #include "singleapplication.h" #include "utils/macosxopenevent.h" #include "utils/qetsettings.h" @@ -206,6 +207,10 @@ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFacto raw_args << QString::fromLocal8Bit(argv[i]); if (CLIExport::isExportRequest(raw_args)) { QApplication export_app(argc, argv); + // No crash-recovery backups in one-shot CLI mode: the backup write + // runs on a background thread referencing the project and races the + // process exit (intermittent segfault in QET::writeToFile). + QETProject::setBackupEnabled(false); return CLIExport::run(export_app.arguments()); } } diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 1f070e3cc..bbcedb6ef 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -43,6 +43,13 @@ static int BACKUP_INTERVAL = 1200000; //interval in ms of backup = 20min +bool QETProject::m_backup_enabled = true; + +void QETProject::setBackupEnabled(bool enabled) +{ + m_backup_enabled = enabled; +} + /** @brief QETProject::QETProject Create a empty project @@ -1783,6 +1790,8 @@ void QETProject::addDiagram(Diagram *diagram, int pos) */ void QETProject::writeBackup() { + if (!m_backup_enabled) + return; #ifdef BUILD_WITHOUT_KF5 #else # if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove diff --git a/sources/qetproject.h b/sources/qetproject.h index e2114244e..d78232de5 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -105,6 +105,12 @@ class QETProject : public QObject QVersionNumber declaredQElectroTechVersion(); void setTitle(const QString &); + /// Enable/disable the asynchronous crash-recovery backup for all + /// projects. Disabled by the headless CLI: the backup write runs on a + /// background thread referencing the project, and a short-lived CLI + /// process can destroy the project before the write finishes (crash). + static void setBackupEnabled(bool enabled); + ///DEFAULT PROPERTIES BorderProperties defaultBorderProperties() const; void setDefaultBorderProperties(const BorderProperties &); @@ -241,6 +247,8 @@ class QETProject : public QObject // attributes private: + /// When false, writeBackup() is a no-op (set by the headless CLI) + static bool m_backup_enabled; /// File path this project is saved to QString m_file_path; /// Current state of the project