Delay handling of KDirWatch signals

Authored by mwolff on Feb 10 2019, 8:39 PM.

Description

Delay handling of KDirWatch signals

This is a workaround for bug 404184, where KDirWatch only emits
a 'deleted' signal during a 'git stash' workflow, which never
gets paired by a 'created' signal, even though the file actually
exists before and after the 'git stash' operation.

By delaying the handling of the KDirWatch signals, we won't fall
into the trap of removing a file even though it actually exists.
Since it doesn't really matter how fast we react on such events,
it's in my opinion fine to delay it for a full second. Having a
correct state is much more important than having any state fast.

Without this patch, KDevelop frequently forgets about files from large
projects I work on after a git stash. This then fubars the C++
support completely, since it cannot find any include paths and
defines anymore.

Details

Committed
mwolffFeb 13 2019, 8:37 AM
Parents
R32:8933d89082bb: Cleanup test_projectload.cpp code
Branches
Unknown
Tags
Unknown
rjvbb added a subscriber: rjvbb.EditedSep 30 2019, 6:38 PM

This delay can also be used as a cheap mechanism to compress events and prevent spurious reloads, something you're bound to get when doing something potentially all-encompassing as checking out a different branch. A mod like the one below is highly effective in my own dirwatching implementation that only monitors directories for changes, but I see a comparable number of skipped redudant reloads when I use stock dirwatching implementation.

diff --git a/kdevplatform/project/abstractfilemanagerplugin.cpp b/kdevplatform/project/abstractfilemanagerplugin.cpp
index 18ba57b99d5c403790a3599c219d500c13b2fb93..b715b6e4fbca66c89ea4cdb6439398476334a415 100644
--- a/kdevplatform/project/abstractfilemanagerplugin.cpp
+++ b/kdevplatform/project/abstractfilemanagerplugin.cpp
@@ -106,6 +106,7 @@ public:
     QHash<IProject*, QList<FileManagerListJob*> > m_projectJobs;
     QVector<QString> m_stoppedFolders;
     ProjectFilterManager m_filters;
+    QVector<QString> m_queuedFolders;
 };
 
 void AbstractFileManagerPluginPrivate::projectClosing(IProject* project)
@@ -488,19 +489,33 @@ ProjectFolderItem *AbstractFileManagerPlugin::import( IProject *project )
         //       I.e. sometimes we used to get a 'delete' event during a rebase which was never
         //       followed up by a 'created' signal, even though the file actually exists after
         //       the rebase.
+        //       This delay can also be used to compress events and prevent spurious reloads:
+        //       if a new event comes in for a folder already queued (or a subdir of one already
+        //       queued), that event can be ignored.
         //       see also: https://bugs.kde.org/show_bug.cgi?id=404184
         connect(watcher, &KDirWatch::created,
                 this, [this] (const QString& path) {
-                    QTimer::singleShot(1000, this, [this, path]() {
-                        Q_D(AbstractFileManagerPlugin);
+                    Q_D(AbstractFileManagerPlugin);
+                    // check if we're a subfolder of a folder that is already queued for reloading
+                    for (const QString& folder : qAsConst(d->m_queuedFolders)) {
+                        if (path.startsWith(folder)) {
+                            return;
+                        }
+                    }
+                    d->m_queuedFolders.append(path);
+                    QTimer::singleShot(1000, this, [d, path]() {
                         d->created(path);
+                        d->m_queuedFolders.removeOne(path);
                     });
                 });
         connect(watcher, &KDirWatch::deleted,
                 this, [this] (const QString& path) {
-                    QTimer::singleShot(1000, this, [this, path]() {
-                        Q_D(AbstractFileManagerPlugin);
+                    Q_D(AbstractFileManagerPlugin);
+                    // queue the path so that any other change signals will be ignored
+                    d->m_queuedFolders.append(path);
+                    QTimer::singleShot(1000, this, [d, path]() {
                         d->deleted(path);
+                        d->m_queuedFolders.removeAll(path);
                     });
                 });
         watcher->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles );