From 06ffddba7949efe23649f3004e8bf5d87fcdcc24 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Tue, 15 Sep 2026 12:05:20 -0700 Subject: [PATCH] Don't stop MSW wxFileSystemWatcher when a watched directory is deleted The IOCP worker thread exited when one of the watched directories was deleted, silently stopping the events for all the other watches of the same wxFileSystemWatcher. Just stop reading the events for the deleted directory and keep processing the other ones. --- src/msw/fswatcher.cpp | 5 +- tests/fswatcher/fswatchertest.cpp | 88 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/msw/fswatcher.cpp b/src/msw/fswatcher.cpp index 6eb5ae776e..8bbebdf082 100644 --- a/src/msw/fswatcher.cpp +++ b/src/msw/fswatcher.cpp @@ -256,8 +256,9 @@ bool wxIOCPThread::ReadEvents() // It isn't useful to continue watching this directory as it // doesn't exist any more -- and even recreating a directory with // the same name still wouldn't resume generating events for the - // existing wxIOCPService, so it's useless to continue. - return false; + // existing wxIOCPService, so don't start a new read for it. But + // keep reading the events for all the other watches, if any. + return true; case wxIOCPService::Status_Exit: return false; // stop reading events diff --git a/tests/fswatcher/fswatchertest.cpp b/tests/fswatcher/fswatchertest.cpp index 66ea98452b..10eeb776e0 100644 --- a/tests/fswatcher/fswatchertest.cpp +++ b/tests/fswatcher/fswatchertest.cpp @@ -925,4 +925,92 @@ TEST_CASE_METHOD(FileSystemWatcherTestCase, tester.Run(); } +// ---------------------------------------------------------------------------- +// TestEventsAfterDeletingWatchedDir +// ---------------------------------------------------------------------------- + +namespace +{ + +// Watches two directories, deletes one of them and checks that the events for the other one are +// still received. +class DeletedDirTester : public wxEvtHandler +{ +public: + DeletedDirTester() : m_timeout(this) + { + Bind(wxEVT_FSWATCHER, &DeletedDirTester::OnFileSystemEvent, this); + Bind(wxEVT_TIMER, &DeletedDirTester::OnTimeout, this); + + // wxFileSystemWatcher can be created only once the event loop is running. + CallAfter(&DeletedDirTester::DoTest); + } + + void Run() { m_loop.Run(); } + + bool GotEventForKeptDir() const { return m_gotEvent; } + +private: + void DoTest() + { + const wxFileName& base = EventGenerator::Get().GetWatchDir(); + + wxFileName deletedDir; + deletedDir.AssignDir(base.GetFullPath()); + deletedDir.AppendDir("deleted"); + REQUIRE( deletedDir.Mkdir() ); + + wxFileName keptDir; + keptDir.AssignDir(base.GetFullPath()); + keptDir.AppendDir("kept"); + REQUIRE( keptDir.Mkdir() ); + + m_watcher.reset(new wxFileSystemWatcher()); + m_watcher->SetOwner(this); + REQUIRE( m_watcher->Add(deletedDir) ); + REQUIRE( m_watcher->Add(keptDir) ); + + REQUIRE( deletedDir.Rmdir() ); + wxMilliSleep(200); + + m_file = wxFileName(keptDir.GetFullPath(), "test.txt"); + wxFile file(m_file.GetFullPath(), wxFile::write); + REQUIRE( file.IsOpened() ); + + // Don't wait forever if the event is never received. + m_timeout.Start(3000, wxTIMER_ONE_SHOT); + } + + void OnFileSystemEvent(wxFileSystemWatcherEvent& evt) + { + if ( evt.GetPath() == m_file ) + { + m_gotEvent = true; + m_loop.Exit(); + } + } + + void OnTimeout(wxTimerEvent& WXUNUSED(evt)) + { + m_loop.Exit(); + } + + wxEventLoop m_loop; + wxTimer m_timeout; + std::unique_ptr m_watcher; + wxFileName m_file; + bool m_gotEvent = false; +}; + +} // anonymous namespace + +TEST_CASE_METHOD(FileSystemWatcherTestCase, + "wxFileSystemWatcher::EventsAfterDeletingWatchedDir", "[fsw]") +{ + DeletedDirTester tester; + tester.Run(); + + CHECK( tester.GotEventForKeptDir() ); +} + #endif // wxUSE_FSWATCHER