diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs index 07edfcf2e2a2d0..bb08525cbd1e10 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs @@ -133,6 +133,7 @@ private sealed class INotify private readonly SafeFileHandle _inotifyHandle; private readonly ConcurrentDictionary _wdToWatch = new ConcurrentDictionary(); private readonly ReaderWriterLockSlim _addLock = new(LockRecursionPolicy.NoRecursion); + private readonly ManualResetEventSlim _readyToRead = new(false); private bool _isThreadStopping; private bool _allWatchersStopped; private int _bufferAvailable; @@ -200,6 +201,11 @@ private void StartThread() } } + /// + /// Waits until the processing thread is ready to read inotify events. + /// + internal void WaitForReadReady() => _readyToRead.Wait(); + private void StopINotify() { // This method gets called only on the ProcessEvents thread, or when that thread fails to start. @@ -489,6 +495,11 @@ private void ProcessEvents() uint movedFromCookie = 0; bool movedFromIsDir = false; + // Signal that the processing thread is ready to read events. + // This ensures callers of Start() can wait until events will + // be captured before performing filesystem operations. + _readyToRead.Set(); + while (TryReadEvent(out NotifyEvent nextEvent)) { if (!ProcessEvent(nextEvent, ref movedFromWatchCount, ref movedFromName, ref movedFromCookie, ref movedFromIsDir)) @@ -509,6 +520,9 @@ private void ProcessEvents() } finally { + // Ensure any caller blocked on WaitForReadReady() is unblocked, + // even if ProcessEvents exits early due to an error. + _readyToRead.Set(); Debug.Assert(_inotifyHandle.IsClosed); } } @@ -1055,6 +1069,12 @@ public void Start() } } + // Ensure the processing thread is ready to read events before + // we start emitting or performing filesystem operations that + // should be observed. Without this, events can be missed if + // they occur before ProcessEvents enters its read loop. + inotify.WaitForReadReady(); + _ = DequeueEvents(); if (rootDirectory is not null && IncludeSubdirectories)