From 6b4b0096fb760283f48d2539b142b4dbf6851f23 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Fri, 11 Sep 2026 17:15:27 +0800 Subject: [PATCH] fix: prevent SVG cache tasks outliving app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Replace the global thread pool with a dedicated IconCachePool for SVG icon cache writes 2. Tie the pool's lifetime to QCoreApplication via QPointer so it is invalidated on shutdown 3. Connect to QCoreApplication::aboutToQuit to clear pending tasks and wait for completion 4. Guard pool creation to the application's main thread and return nullptr once stopped 5. Skip cache writes entirely when no valid pool is available to avoid dangling tasks Influence: 1. Verify SVG icons still render correctly and cache files are written under normal use 2. Test application shutdown while many icon cache tasks are queued, ensure no crashes or warnings about destroyed threads 3. Confirm no tasks run on a destroyed QCoreApplication and no leaks are reported 4. Test cache file creation, commit, and timestamp updates remain functional 5. Verify behavior when pixmap rendering happens from non-main threads (pool should be skipped) 6. Check that icons without cache file paths still render without regressions fix: 防止 SVG 缓存任务超出应用生命周期 1. 使用专用的 IconCachePool 替代全局线程池来处理 SVG 图标缓存写入 2. 通过 QPointer 将线程池生命周期绑定到 QCoreApplication,在退出时自动 失效 3. 连接 QCoreApplication::aboutToQuit,清除待处理任务并等待完成 4. 限制线程池仅在应用主线程创建,停止后返回 nullptr 5. 当没有可用线程池时跳过缓存写入,避免产生悬空任务 Influence: 1. 验证正常使用下 SVG 图标仍能正确渲染并写入缓存文件 2. 测试在大量图标缓存任务排队时关闭应用,确保不会崩溃或出现线程已销毁的 警告 3. 确认不会在已销毁的 QCoreApplication 上运行任务,且无内存泄漏报告 4. 测试缓存文件的创建、提交和时间戳更新功能仍然正常 5. 验证从非主线程渲染 pixmap 时的行为(应跳过线程池) 6. 检查无缓存文件路径的图标仍能正常渲染,无回归问题 --- .../svgiconengine/qsvgiconengine.cpp | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/iconengineplugins/svgiconengine/qsvgiconengine.cpp b/iconengineplugins/svgiconengine/qsvgiconengine.cpp index 4e6fd5dd..35749c47 100644 --- a/iconengineplugins/svgiconengine/qsvgiconengine.cpp +++ b/iconengineplugins/svgiconengine/qsvgiconengine.cpp @@ -16,9 +16,14 @@ #include #include +#include #include +#include #include +#include #include +#include +#include #include #include @@ -32,6 +37,49 @@ Q_LOGGING_CATEGORY(lcDSvg, "dde.dsvg") Q_LOGGING_CATEGORY(lcDSvg, "dde.dsvg", QtInfoMsg) #endif +namespace { + +class IconCachePool final : public QThreadPool +{ +public: + template + static QFuture run(Function function) + { + if (IconCachePool *pool = instance()) + return QtConcurrent::run(pool, function); + return QFuture(); + } + +private: + static IconCachePool *instance() + { + QCoreApplication *application = QCoreApplication::instance(); + if (!application || QThread::currentThread() != application->thread()) + return nullptr; + + static QPointer pool; + if (pool && pool->stopped) + return nullptr; + if (!pool) + pool = new IconCachePool(application); + return pool; + } + + explicit IconCachePool(QCoreApplication *application) + : QThreadPool(application) + { + connect(application, &QCoreApplication::aboutToQuit, this, [this] { + stopped = true; + clear(); + waitForDone(); + }); + } + + bool stopped = false; +}; + +} // namespace + class QSvgIconEnginePrivate : public QSharedData { public: @@ -232,7 +280,7 @@ QPixmap QSvgIconEngine::pixmap(const QSize &size, QIcon::Mode mode, const QImage image = renderer.toImage(actualSize); if (Q_LIKELY(!image.isNull() && !cacheFile.isEmpty())) { - auto result = QtConcurrent::run(QThreadPool::globalInstance(), [image, cacheFile, svgFile] { + auto result = IconCachePool::run([image, cacheFile, svgFile] { QSaveFile file(cacheFile); // 增加cache文件能被成功保存的概率 file.setDirectWriteFallback(true);