From 8d0e4a35e328e9ef01cce6abf3dd2cfca1466086 Mon Sep 17 00:00:00 2001 From: xiepengfei Date: Fri, 9 Oct 2026 12:08:39 +0800 Subject: [PATCH] fix: make StartManager lifetime explicit to prevent exit crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Root cause: StartManager::instance() lazily creates the singleton, and ~EditorApplication() called it during teardown; a forwarding (second) process that never created StartManager would instantiate a full instance (login1 inhibit D-Bus call, QTimer, Iflytek AI probe QtConcurrent task) and destroy it right away, leaving the worker thread logging during process teardown and crashing in libdtk6log (SIGSEGV) 2. Fix: - instance() is now a pure non-creating accessor - creation is explicit via create(), called exactly once in main() - the first (service-owning) process releases the singleton in main() right after the event loop ends; ~EditorApplication() no longer touches StartManager at all 3. Impact: the forwarding process exit path never instantiates StartManager, removing the teardown crash; accidental singleton creation is now impossible for all callers by construction Log: Fixed crash when opening a text file from desktop with the editor already running Influence: 1. Test opening a text file by double-clicking on desktop with editor open 2. Test launching a second instance that forwards files via D-Bus 3. Test single-window startup and normal exit of the editor 4. Run StartManager / EditorApplication / Window / Controls unit tests (updated for create() / non-creating instance() semantics) fix: 单例生命周期显式化,修复退出崩溃 1. 根因: StartManager::instance() 是懒创建访问器,~EditorApplication() 在退出期调用它;D-Bus 转发进程从未创建过单例,析构时意外实例化完整 StartManager(login1 inhibit 同步 D-Bus 调用、QTimer、Iflytek AI 探测 QtConcurrent 任务)并立即销毁,工作线程在进程 teardown 期间打日志, 于 libdtk6log 中 SIGSEGV 2. 方案: - instance() 改为纯查询访问器,不再有创建副作用 - 创建显式化为 create(),仅在 main() 中调用一次 - 首进程在事件循环结束后于 main() 中显式释放单例, ~EditorApplication() 不再触碰 StartManager 3. 影响: 转发进程退出路径不再实例化 StartManager,消除崩溃;对所有 调用方而言,意外创建单例在构造上已不可能 Log: 修复已打开编辑器时双击桌面文本文档产生的崩溃 Influence: 1. 测试已打开编辑器时双击桌面文本文档 2. 测试第二进程通过 D-Bus 转发文件后退出 3. 测试单窗口启动与正常退出 4. 运行 StartManager / EditorApplication / Window / Controls 单元测试 (已随 create() / 非创建 instance() 语义同步更新) PMS: BUG-378901 --- src/editorapplication.cpp | 11 +++-------- src/main.cpp | 9 +++++++-- src/startmanager.cpp | 7 ++++++- src/startmanager.h | 2 ++ tests/app_ut/test_editorapplication.cpp | 22 +++++++++++++--------- tests/startmanager/test_startmanager.cpp | 20 ++++++++++++++++---- tests/widgets_ut/test_window.cpp | 5 +++++ 7 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/editorapplication.cpp b/src/editorapplication.cpp index daec65d41..e2067ffde 100644 --- a/src/editorapplication.cpp +++ b/src/editorapplication.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -45,13 +45,8 @@ EditorApplication::EditorApplication(int &argc, char *argv[]) : DApplication(arg EditorApplication::~EditorApplication() { qDebug() << "Enter EditorApplication destructor"; - // app结束时,释放 - if (nullptr != StartManager::instance()) { - qDebug() << "Deleting StartManager instance"; - delete StartManager::instance(); - } else { - qDebug() << "StartManager instance is already null"; - } + // StartManager 生命周期由 main() 接管(创建于 create(),事件循环结束后显式释放), + // 析构不得触碰,否则 D-Bus 转发进程会在退出期意外实例化单例(BUG-378901) qDebug() << "Exit EditorApplication destructor"; qInfo() << "Application resources released"; } diff --git a/src/main.cpp b/src/main.cpp index ef06e7e71..4ccf78b67 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,7 +133,8 @@ int main(int argc, char *argv[]) qDebug() << "DTKWIDGET_CLASS_DSizeMode not defined, skipping size mode handler"; #endif - StartManager *startManager = StartManager::instance(); + // 单例唯一创建点;D-Bus 转发进程走不到这里,不会实例化 StartManager + StartManager *startManager = StartManager::create(); //埋点记录启动数据 QJsonObject objStartEvent{ @@ -169,7 +170,11 @@ int main(int argc, char *argv[]) PerformanceMonitor::initializeAppFinish(); qDebug() << "Entering main event loop"; - return app.exec(); + int ret = app.exec(); + // 事件循环已停止,在此显式释放单例;~EditorApplication 不再管理其生命周期 + qDebug() << "Deleting StartManager instance on exit"; + delete StartManager::instance(); + return ret; } // Just send dbus message to exist editor process. else { diff --git a/src/startmanager.cpp b/src/startmanager.cpp index a5c896bee..232dc5d33 100644 --- a/src/startmanager.cpp +++ b/src/startmanager.cpp @@ -32,7 +32,7 @@ static const QString s_bookMarkKey = "advance.editor.bookmark"; StartManager *StartManager::m_instance = nullptr; -StartManager *StartManager::instance() +StartManager *StartManager::create() { if (m_instance == nullptr) { qDebug() << "StartManager instance is null, creating new instance"; @@ -42,6 +42,11 @@ StartManager *StartManager::instance() return m_instance; } +StartManager *StartManager::instance() +{ + return m_instance; +} + StartManager::~StartManager() { qDebug() << "Enter StartManager destructor"; diff --git a/src/startmanager.h b/src/startmanager.h index 12dd2a974..1f6f94bac 100644 --- a/src/startmanager.h +++ b/src/startmanager.h @@ -29,6 +29,8 @@ class StartManager : public QObject int tabIndex; }; + // 唯一创建点:仅 main() 调用;其余场合一律用 instance() 纯查询 + static StartManager *create(); static StartManager *instance(); explicit StartManager(QObject *parent = nullptr); ~StartManager(); diff --git a/tests/app_ut/test_editorapplication.cpp b/tests/app_ut/test_editorapplication.cpp index 17a6adc72..857b60162 100644 --- a/tests/app_ut/test_editorapplication.cpp +++ b/tests/app_ut/test_editorapplication.cpp @@ -13,8 +13,7 @@ // 分支清单 → 用例映射(editorapplication.cpp): // ctor(9) → SetUpTestSuite 真实构造 + Ctor_应用属性就位(Qt>=6 分支) // dtor(45) → TearDownTestSuite delete s_app: -// D2/D0 双记录 + "StartManager::instance() 非空 → delete"分支 -// (else 空分支不可二次构造 QApplication,函数覆盖已达成) +// D2/D0 双记录(StartManager 生命周期已移交 main(),dtor 不再触碰) // handleQuitAction(59) // B1 activeWindow 非空 → close // → HandleQuitAction_ActiveWindow_ClosesIt(closeEvent 计数 + 隐藏断言) @@ -444,14 +443,19 @@ TEST_F(EditorApplicationTest, PressSpace_Button_ReleasesAfter80msAndClicks) // ---------------- dtor 前置 ---------------- -// dtor(45) 前置:保证 TearDownTestSuite delete s_app 时 StartManager::instance() -// 非空,命中“Deleting StartManager instance”分支(D2/D0 双记录在 delete 时落点) -TEST_F(EditorApplicationTest, Dtor_Precondition_StartManagerInstanceAlive) +// dtor(45) 前置:~EditorApplication 已不管理 StartManager 生命周期(BUG-378901 修复: +// 创建点唯一化为 main() 中的 create(),事件循环结束后于 main() 显式释放)。 +// 此处验证 instance() 为纯查询访问器,无创建副作用——TearDownTestSuite delete s_app +// 时不会意外实例化/销毁单例(D2/D0 双记录在 delete 时落点) +TEST_F(EditorApplicationTest, Dtor_Precondition_InstanceIsNonCreating) { - // Arrange / Act:惰性单例真实构造(DBus/Iflytek 已 stub,XDG 已重定向) + // Arrange + StartManager::m_instance = nullptr; + + // Act:纯查询访问器(旧实现此处会惰性创建完整单例) StartManager *instance = StartManager::instance(); - // Assert - EXPECT_NE(instance, nullptr); - EXPECT_EQ(StartManager::m_instance, instance); + // Assert:不创建,析构路径对 StartManager 零接触 + EXPECT_EQ(instance, nullptr); + EXPECT_EQ(StartManager::m_instance, nullptr); } diff --git a/tests/startmanager/test_startmanager.cpp b/tests/startmanager/test_startmanager.cpp index 28d41ff96..6dc09ad9b 100644 --- a/tests/startmanager/test_startmanager.cpp +++ b/tests/startmanager/test_startmanager.cpp @@ -548,18 +548,20 @@ QCoreApplication *StartManagerTest::s_app = nullptr; // 构造 / 单例 / 析构 // ============================================================ -TEST_F(StartManagerTest, Instance_FirstCall_CreatesAndReusesSingleton) +TEST_F(StartManagerTest, Create_FirstCall_CreatesAndReusesSingleton) { // Arrange StartManager::m_instance = nullptr; - // Act - StartManager *p1 = StartManager::instance(); - StartManager *p2 = StartManager::instance(); + // Act:create() 为唯一创建点,重复调用幂等;instance() 纯查询同实例 + StartManager *p1 = StartManager::create(); + StartManager *p2 = StartManager::create(); + StartManager *q = StartManager::instance(); // Assert:首建非空且复用同一实例 ASSERT_NE(p1, nullptr); EXPECT_EQ(p1, p2); + EXPECT_EQ(q, p1); // Cleanup:析构置空静态指针(覆盖 ~StartManager B2 正侧) delete p1->m_pTimer; @@ -567,6 +569,16 @@ TEST_F(StartManagerTest, Instance_FirstCall_CreatesAndReusesSingleton) EXPECT_EQ(StartManager::m_instance, nullptr); } +TEST_F(StartManagerTest, Instance_NonCreating_ReturnsNullWhenAbsent) +{ + // Arrange:BUG-378901 修复后 instance() 不再惰性创建 + StartManager::m_instance = nullptr; + + // Act / Assert:单例缺席时纯查询返回空,不得实例化 + EXPECT_EQ(StartManager::instance(), nullptr); + EXPECT_EQ(StartManager::m_instance, nullptr); +} + TEST_F(StartManagerTest, Destructor_ForeignInstance_KeepsStaticPointer) { // Arrange:静态指针指向其他实例 diff --git a/tests/widgets_ut/test_window.cpp b/tests/widgets_ut/test_window.cpp index 8d6af7ef1..28e44d2dd 100644 --- a/tests/widgets_ut/test_window.cpp +++ b/tests/widgets_ut/test_window.cpp @@ -93,6 +93,11 @@ class WindowTest : public ::testing::Test { QDir().mkpath(xdgData + "/deepin/deepin-editor/blank-files"); QDir().mkpath(xdgData + "/deepin/deepin-editor/backup-files"); QDir().mkpath(xdgData + "/deepin/deepin-editor/autoBackup-files"); + + // BUG-378901 修复后 instance() 为纯查询不创建,window.cpp 各调用点 + // (checkTabbarForReload/removeWrapper 的 delayMallocTrim 等)依赖单例已存在; + // 套件级显式建真实例,与旧懒创建语义等价(生产路径由 main() 中 create() 保证) + StartManager::create(); } static void TearDownTestSuite()