From c36f0b983f004e969ca56c13e3ed8ce9eb65e7bc Mon Sep 17 00:00:00 2001 From: reionwong Date: Sat, 17 Oct 2026 12:49:27 +0800 Subject: [PATCH] fix(files): back off the trash monitor's retries while a watch keeps failing --- files/tests/files_test.cpp | 41 ++++++++++++++++++++++++++++++++++++++ files/trashmonitor.cpp | 15 +++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/files/tests/files_test.cpp b/files/tests/files_test.cpp index bdad2d2..cad89de 100644 --- a/files/tests/files_test.cpp +++ b/files/tests/files_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -931,6 +932,46 @@ TEST_F(FilesTest, TrashMonitorSeesVolumes) EXPECT_TRUE(waitFor(loop, [&] { return !monitor.isEmpty(); })); } +// A trash folder that cannot be watched is looked for again less and less often; +// each look reads the mount table. +TEST_F(FilesTest, TrashMonitorBacksOffFailingWatch) +{ + TemporaryDirectory dir; + writeFile(dir.filePath("local")); + OperationContext ctx; + LocalFileSystem::trash(local(dir.filePath("local")), ctx); + + const auto files = FileSystemUrl::trashRoot() + "/files"; + ASSERT_EQ(::chmod(files.c_str(), 0), 0); + struct Restore + { + std::string path; + ~Restore() { ::chmod(path.c_str(), 0700); } + } restore{files}; + + const int inotify = ::inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + ASSERT_GE(inotify, 0); + ASSERT_GE(::inotify_add_watch(inotify, mountTable().c_str(), IN_OPEN), 0); + + // Repeated reads of the table merge into one event until the queue is read. + int looks = 0; + { + TrashMonitor monitor; + for (int i = 0; i < 95; ++i) { + wait(loop, 100ms); + + inotify_event events[64]; + if (::read(inotify, events, sizeof(events)) > 0) + ++looks; + } + } + ::close(inotify); + + // At the start, after two seconds and after four more. + EXPECT_GE(looks, 2); + EXPECT_LE(looks, 3); +} + // A tmpfs in a private user and mount namespace stands in for a second drive. TEST_F(FilesTest, TrashOnAnotherFilesystem) { diff --git a/files/trashmonitor.cpp b/files/trashmonitor.cpp index d41aac3..4ab3793 100644 --- a/files/trashmonitor.cpp +++ b/files/trashmonitor.cpp @@ -3,6 +3,7 @@ #include "files/trashmonitor.h" +#include #include #include #include @@ -13,6 +14,9 @@ namespace cutefish::files { using namespace std::chrono_literals; +static constexpr std::chrono::milliseconds FirstRetry = 2000ms; +static constexpr std::chrono::milliseconds LastRetry = 60000ms; + static bool hasEntries(const std::string& path) { DIR* dir = ::opendir(path.c_str()); @@ -35,7 +39,8 @@ TrashMonitor::TrashMonitor() { m_debounce.setSingleShot(true); m_debounce.setInterval(40ms); - m_retry.setInterval(2000ms); + m_retry.setSingleShot(true); + m_retry.setInterval(FirstRetry); m_watchChanged = m_watch.changed.connect([this] { m_debounce.start(); @@ -82,10 +87,14 @@ void TrashMonitor::refresh() paths.push_back(path); } - if (m_watch.watch(paths, state.entries, true)) + // A watch that keeps failing, past the inotify limit say, is tried less often. + if (m_watch.watch(paths, state.entries, true)) { m_retry.stop(); - else + m_retry.setInterval(FirstRetry); + } else { m_retry.start(); + m_retry.setInterval(std::min(m_retry.interval() * 2, LastRetry)); + } if (state.empty != m_empty) { m_empty = state.empty;