[network/ktorrent] plugins/scanfolder: Fix crash in ScanFolder plugin due to non-threadsafe KDirWatch destructor

Jack Hill <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 16b1ff640d147552d5300871c71499d0f15f1698 by Jack Hill.
Committed on 19/07/2026 at 15:23.
Pushed by jackh into branch 'master'.

Fix crash in ScanFolder plugin due to non-threadsafe KDirWatch destructor

KDirWatch has an internal QThreadLocalStorage object which is cleaned
up automatically when the last KDirWatch is deleted. Therefore
KDirWatch is not thread-safe, and each ScanFolder must be deleted in
its own thread. stop() can be called from any thread, so use
deleteLater() instead of normal delete.

To reproduce:
1. Enable Scan Folder plugin
2. Open the settings page and add a folder, doesn't need to contain
   any files
3. Disable the Scan Folder plugin

Before:
- Crash

Now:
- No crash

Fixes sentry issue KTORRENT-W8

M  +12   -11   plugins/scanfolder/scanthread.cpp
M  +1    -1    plugins/scanfolder/scanthread.h

https://invent.kde.org/network/ktorrent/-/commit/16b1ff640d147552d5300871c71499d0f15f1698

diff --git a/plugins/scanfolder/scanthread.cpp b/plugins/scanfolder/scanthread.cpp
index 676fb146d..4ce195691 100644
--- a/plugins/scanfolder/scanthread.cpp
+++ b/plugins/scanfolder/scanthread.cpp
@@ -52,7 +52,6 @@ ScanThread::ScanThread()
     : stop_requested(false)
     , recursive(false)
 {
-    scan_folders.setAutoDelete(true);
     moveToThread(this);
 }
 
@@ -100,20 +99,19 @@ void ScanThread::updateFolders()
     mutex.unlock();
 
     // first erase folders we don't need anymore
-    bt::PtrMap<QString, ScanFolder>::iterator i = scan_folders.begin();
+    auto i = scan_folders.begin();
     while (i != scan_folders.end()) {
-        if (!tmp.contains(i->first)) {
-            QString f = i->first;
-            i++;
-            scan_folders.erase(f);
+        if (!tmp.contains(i.key())) {
+            i.value()->deleteLater();
+            i = scan_folders.erase(i);
         } else {
-            i->second->setRecursive(recursive);
+            i.value()->setRecursive(recursive);
             i++;
         }
     }
 
     for (const QString &folder : std::as_const(tmp)) {
-        if (scan_folders.find(folder)) {
+        if (scan_folders.value(folder)) {
             continue;
         }
 
@@ -135,9 +133,12 @@ void ScanThread::stop()
 {
     stop_requested = true;
 
-    // XXX seems like deleting KDirWatch object(s) created in scan_folders
-    // in destructor of this QThread after it has been stopped
-    // causes memory corruption, so we delete them early
+    // Make sure QObjects are deleted in their own thread.
+    // Also KDirWatch uses thread-local storage which must
+    // be cleaned up by deleting in the proper thread.
+    for (auto *scan_folder : scan_folders) {
+        scan_folder->deleteLater();
+    }
     scan_folders.clear();
     exit();
     wait();
diff --git a/plugins/scanfolder/scanthread.h b/plugins/scanfolder/scanthread.h
index db1b282e5..12da8a660 100644
--- a/plugins/scanfolder/scanthread.h
+++ b/plugins/scanfolder/scanthread.h
@@ -75,7 +75,7 @@ private:
     QStringList folders;
     std::atomic<bool> stop_requested;
     std::atomic<bool> recursive;
-    bt::PtrMap<QString, ScanFolder> scan_folders;
+    QMap<QString, ScanFolder *> scan_folders;
 };
 
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.