[frameworks/kio] /: kdirlister: hold three directories in the lister cache, and not for long
Méven Car <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 5e36acccb3ec5ed5e1d049e697c6639a7f54bc65 by Méven Car.
Committed on 30/07/2026 at 09:20.
Pushed by meven into branch 'master'.
kdirlister: hold three directories in the lister cache, and not for long
A directory that no lister holds any more went into a cache of ten, whatever it held, and stayed there
until nine others had come and gone. An application that shows a listing of 50000 files, as a picture
folder is, held the items of ten of them: near 500 MB of KFileItem and UDSEntry that nothing on screen
was showing any more.
The cache now holds three directories, which is what going back a level or two asks of it, and drops one
that no lister has wanted for three minutes. What is held for the way back is held for the time a way
back takes.
The lifetime is the one thing a test cannot wait for, so it says how long to keep a directory while
BUILD_TESTING is set, and the cache follows it.
M +28 -2 autotests/kdirlistertest.cpp
M +1 -0 autotests/kdirlistertest.h
M +43 -3 src/core/kcoredirlister.cpp
M +11 -0 src/core/kcoredirlister_p.h
https://invent.kde.org/frameworks/kio/-/commit/5e36acccb3ec5ed5e1d049e697c6639a7f54bc65
diff --git a/autotests/kdirlistertest.cpp b/autotests/kdirlistertest.cpp
index 40d7853f93..d45472757a 100644
--- a/autotests/kdirlistertest.cpp
+++ b/autotests/kdirlistertest.cpp
@@ -1722,10 +1722,36 @@ void KDirListerTest::testCacheEviction()
QVERIFY(KDirWatch::self()->contains(newDirPath));
}
- // watches were removed as the dirItem were evicted from cache
+ // watches were removed as the dirItem were evicted from cache, which keeps three directories
QVERIFY(!KDirWatch::self()->contains(newDir.path()));
QVERIFY(!KDirWatch::self()->contains(newDir.path() + QString("dir_0")));
- QVERIFY(KDirWatch::self()->contains(newDir.path() + QString("dir_1")));
+ QVERIFY(!KDirWatch::self()->contains(newDir.path() + QString("dir_1")));
+ QVERIFY(KDirWatch::self()->contains(newDir.path() + QString("dir_10")));
+}
+
+void KDirListerTest::testCacheExpiry()
+{
+ qputenv("KIO_DIRLISTER_CACHE_LIFETIME_MS", "300");
+ auto restore = qScopeGuard([]() {
+ qunsetenv("KIO_DIRLISTER_CACHE_LIFETIME_MS");
+ });
+
+ QTemporaryDir newDir(homeTmpDir());
+ const QString cachedDirPath = newDir.path() + QString("cached");
+ QVERIFY(QDir().mkdir(cachedDirPath));
+
+ MyDirLister dirLister;
+ dirLister.openUrl(QUrl::fromLocalFile(cachedDirPath));
+ QVERIFY(dirLister.spyCompleted.wait(500));
+ QVERIFY(KDirWatch::self()->contains(cachedDirPath));
+
+ // Leaving the directory puts it in the cache, where it is kept for as long as the lifetime this
+ // test asked for when it set KIO_DIRLISTER_CACHE_LIFETIME_MS.
+ dirLister.openUrl(QUrl::fromLocalFile(newDir.path()));
+ QVERIFY(dirLister.spyCompleted.wait(500));
+ QVERIFY(KDirWatch::self()->contains(cachedDirPath));
+
+ QTRY_VERIFY_WITH_TIMEOUT(!KDirWatch::self()->contains(cachedDirPath), 5000);
}
void KDirListerTest::testUnreadableParentDirectory()
diff --git a/autotests/kdirlistertest.h b/autotests/kdirlistertest.h
index 5813126205..5edcb485c0 100644
--- a/autotests/kdirlistertest.h
+++ b/autotests/kdirlistertest.h
@@ -112,6 +112,7 @@ private Q_SLOTS:
void testMimeFilter();
void testBug386763();
void testCacheEviction();
+ void testCacheExpiry();
void testUnreadableParentDirectory();
void testPathWithSquareBrackets();
void testSFTPRedirect();
diff --git a/src/core/kcoredirlister.cpp b/src/core/kcoredirlister.cpp
index ab0de89973..b9f7c8ba92 100644
--- a/src/core/kcoredirlister.cpp
+++ b/src/core/kcoredirlister.cpp
@@ -42,13 +42,25 @@ Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf.kio.core.dirlister", QtWarningMsg)
QThreadStorage<KCoreDirListerCache> s_kDirListerCache;
+// A directory that no lister holds any more is kept for a while, so that going back to it is listed
+// from memory. Three of them cover the way back, and one that has not been wanted for this long is
+// dropped, so that the items of a listing left behind are not held for the life of the application.
+static constexpr int s_maxCachedDirectories = 3;
+static constexpr std::chrono::minutes s_cachedDirectoryLifetime{3};
+
+// Keep the last few directories' ".hidden" files around, so revisiting one does not re-read it.
+static constexpr int s_maxCachedDotHiddenFiles = 10;
+
KCoreDirListerCache::KCoreDirListerCache()
- : itemsCached(10)
- , // keep the last 10 directories around
- m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around
+ : itemsCached(s_maxCachedDirectories)
+ , m_cacheHiddenFiles(s_maxCachedDotHiddenFiles)
{
qCDebug(KIO_CORE_DIRLISTER);
+ cachedDirectoryLifetime = s_cachedDirectoryLifetime;
+ cachePruneTimer.setInterval(cachedDirectoryLifetime);
+ connect(&cachePruneTimer, &QTimer::timeout, this, &KCoreDirListerCache::pruneCachedDirectories);
+
connect(&pendingUpdateTimer, &QTimer::timeout, this, &KCoreDirListerCache::processPendingUpdates);
pendingUpdateTimer.setSingleShot(true);
@@ -533,7 +545,15 @@ void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, b
// Inserting into QCache must be done last, since it might delete the item
if (item && insertIntoCache) {
qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url;
+ item->lastWanted = std::chrono::steady_clock::now();
itemsCached.insert(url, item);
+#ifdef BUILD_TESTING
+ // A test cannot wait minutes for a directory to be dropped, so it says how long to keep one.
+ const int ms = qEnvironmentVariableIntValue("KIO_DIRLISTER_CACHE_LIFETIME_MS");
+ cachedDirectoryLifetime = ms > 0 ? std::chrono::milliseconds(ms) : std::chrono::milliseconds(s_cachedDirectoryLifetime);
+ cachePruneTimer.setInterval(cachedDirectoryLifetime);
+#endif
+ cachePruneTimer.start();
}
}
@@ -667,10 +687,30 @@ KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir
DirItem *item = itemsInUse.value(url);
if (!item) {
item = itemsCached[url];
+ if (item) {
+ item->lastWanted = std::chrono::steady_clock::now();
+ }
}
return item;
}
+void KCoreDirListerCache::pruneCachedDirectories()
+{
+ const auto now = std::chrono::steady_clock::now();
+ const QList<QUrl> cached = itemsCached.keys();
+ for (const QUrl &url : cached) {
+ const DirItem *item = itemsCached.object(url);
+ if (item && now - item->lastWanted >= cachedDirectoryLifetime) {
+ qCDebug(KIO_CORE_DIRLISTER) << "dropping" << url << "from the cache";
+ itemsCached.remove(url);
+ }
+ }
+
+ if (itemsCached.isEmpty()) {
+ cachePruneTimer.stop();
+ }
+}
+
QList<KFileItem> *KCoreDirListerCache::itemsForDir(const QUrl &dir) const
{
DirItem *item = dirItemForUrl(dir);
diff --git a/src/core/kcoredirlister_p.h b/src/core/kcoredirlister_p.h
index 518f7fd60f..70fc8b1375 100644
--- a/src/core/kcoredirlister_p.h
+++ b/src/core/kcoredirlister_p.h
@@ -23,6 +23,8 @@
#include <QTimer>
#include <QUrl>
+#include <chrono>
+
#include <KDirWatch>
#include <kio/global.h>
@@ -257,6 +259,8 @@ public Q_SLOTS:
void slotFileRenamed(const QString &srcUrl, const QString &dstUrl, const QString &dstPath);
private Q_SLOTS:
+ // drops the cached directories that no lister has wanted for a while
+ void pruneCachedDirectories();
void slotFileDirty(const QString &_file);
void slotFileCreated(const QString &_file);
void slotFileDeleted(const QString &_file);
@@ -393,6 +397,7 @@ private:
DirItem(const QUrl &dir, const QString &canonicalPath)
: url(dir)
, m_canonicalPath(canonicalPath)
+ , lastWanted(std::chrono::steady_clock::now())
{
autoUpdates = 0;
complete = false;
@@ -513,6 +518,9 @@ private:
// the local path, with symlinks resolved, so that KDirWatch works
QString m_canonicalPath;
+ // when a lister last held this directory or looked for it in the cache
+ std::chrono::steady_clock::time_point lastWanted;
+
// KFileItem representing the root of this directory.
// Remember that this is optional. FTP sites don't return '.' in
// the list, so they give no root item
@@ -544,6 +552,9 @@ private:
std::set<QString /*path*/> pendingDirectoryUpdates;
// The timer for doing the delayed updates
QTimer pendingUpdateTimer;
+ QTimer cachePruneTimer;
+ // how long a cached directory is kept, which a test shortens
+ std::chrono::milliseconds cachedDirectoryLifetime;
// Set of remote files that have changed recently -- but we can't emit those
// changes yet, we need to wait for the "update" directory listing.