Skip to content

Commit 16bf014

Browse files
committed
Simplify polling watcher
1 parent b9a18da commit 16bf014

File tree

2 files changed

+49
-49
lines changed

2 files changed

+49
-49
lines changed

src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingDirectoryWatcher.cs

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ internal sealed class PollingDirectoryWatcher : IDirectoryWatcher
1313
private readonly DirectoryInfo _watchedDirectory;
1414
private readonly bool _includeSubdirectories;
1515

16-
private Dictionary<string, FileMeta> _knownFiles = new(PathUtilities.OSSpecificPathComparer);
17-
private Dictionary<string, FileMeta> _tempDictionary = new(PathUtilities.OSSpecificPathComparer);
18-
private readonly Dictionary<string, ChangeKind> _changes = new(PathUtilities.OSSpecificPathComparer);
16+
private Dictionary<string, DateTime> _currentSnapshot = new(PathUtilities.OSSpecificPathComparer);
17+
18+
// The following are sets that are used to calculate new snapshot and cleared on eached use (pooled):
19+
private Dictionary<string, DateTime> _snapshotBuilder = new(PathUtilities.OSSpecificPathComparer);
20+
private readonly Dictionary<string, ChangeKind> _changesBuilder = new(PathUtilities.OSSpecificPathComparer);
1921

2022
private Thread _pollingThread;
2123
private bool _raiseEvents;
@@ -42,7 +44,7 @@ public PollingDirectoryWatcher(string watchedDirectory, bool includeSubdirectori
4244
Name = nameof(PollingDirectoryWatcher)
4345
};
4446

45-
CreateKnownFilesSnapshot();
47+
CaptureInitialSnapshot();
4648

4749
_pollingThread.Start();
4850
}
@@ -91,67 +93,53 @@ private void PollingLoop()
9193
stopwatch.Stop();
9294
}
9395

94-
private void CreateKnownFilesSnapshot()
96+
private void CaptureInitialSnapshot()
9597
{
96-
_knownFiles.Clear();
98+
Debug.Assert(_currentSnapshot.Count == 0);
9799

98-
ForeachEntityInDirectory(_watchedDirectory, fileInfo =>
100+
ForeachEntityInDirectory(_watchedDirectory, (filePath, writeTime) =>
99101
{
100-
_knownFiles.Add(fileInfo.FullName, new FileMeta(fileInfo, foundAgain: false));
102+
_currentSnapshot.Add(filePath, writeTime);
101103
});
102104
}
103105

104106
private void CheckForChangedFiles()
105107
{
106-
_changes.Clear();
108+
Debug.Assert(_changesBuilder.Count == 0);
109+
Debug.Assert(_snapshotBuilder.Count == 0);
107110

108-
ForeachEntityInDirectory(_watchedDirectory, fileInfo =>
111+
ForeachEntityInDirectory(_watchedDirectory, (filePath, currentWriteTime) =>
109112
{
110-
var fullFilePath = fileInfo.FullName;
111-
112-
if (_knownFiles.TryGetValue(fullFilePath, out var fileMeta))
113+
if (!_currentSnapshot.TryGetValue(filePath, out var snapshotWriteTime))
113114
{
114-
try
115-
{
116-
if (fileMeta.FileInfo.LastWriteTime != fileInfo.LastWriteTime)
117-
{
118-
// File changed
119-
_changes.TryAdd(fullFilePath, ChangeKind.Update);
120-
}
121-
122-
_knownFiles[fullFilePath] = new FileMeta(fileMeta.FileInfo, foundAgain: true);
123-
}
124-
catch (FileNotFoundException)
125-
{
126-
_knownFiles[fullFilePath] = new FileMeta(fileMeta.FileInfo, foundAgain: false);
127-
}
115+
_changesBuilder.TryAdd(filePath, ChangeKind.Add);
128116
}
129-
else
117+
else if (snapshotWriteTime != currentWriteTime)
130118
{
131-
// File added
132-
_changes.TryAdd(fullFilePath, ChangeKind.Add);
119+
_changesBuilder.TryAdd(filePath, ChangeKind.Update);
133120
}
134121

135-
_tempDictionary.Add(fileInfo.FullName, new FileMeta(fileInfo, foundAgain: false));
122+
_snapshotBuilder.Add(filePath, currentWriteTime);
136123
});
137124

138-
foreach (var (fullPath, fileMeta) in _knownFiles)
125+
foreach (var (filePath, _) in _currentSnapshot)
139126
{
140-
if (!fileMeta.FoundAgain)
127+
if (!_snapshotBuilder.ContainsKey(filePath))
141128
{
142-
// File deleted
143-
_changes.TryAdd(fullPath, ChangeKind.Delete);
129+
_changesBuilder.TryAdd(filePath, ChangeKind.Delete);
144130
}
145131
}
146132

147-
NotifyChanges();
133+
NotifyChanges(_changesBuilder);
148134

149135
// Swap the two dictionaries
150-
(_tempDictionary, _knownFiles) = (_knownFiles, _tempDictionary);
151-
_tempDictionary.Clear();
136+
(_snapshotBuilder, _currentSnapshot) = (_currentSnapshot, _snapshotBuilder);
137+
138+
_changesBuilder.Clear();
139+
_snapshotBuilder.Clear();
152140
}
153141

154-
private void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action<FileSystemInfo> fileAction)
142+
private void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action<string, DateTime> fileAction)
155143
{
156144
if (!dirInfo.Exists)
157145
{
@@ -180,14 +168,26 @@ private void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action<FileSystemIn
180168
}
181169
else
182170
{
183-
fileAction(entity);
171+
string filePath;
172+
DateTime currentWriteTime;
173+
try
174+
{
175+
filePath = entity.FullName;
176+
currentWriteTime = entity.LastWriteTimeUtc;
177+
}
178+
catch (FileNotFoundException)
179+
{
180+
continue;
181+
}
182+
183+
fileAction(filePath, currentWriteTime);
184184
}
185185
}
186186
}
187187

188-
private void NotifyChanges()
188+
private void NotifyChanges(Dictionary<string, ChangeKind> changes)
189189
{
190-
foreach (var (path, kind) in _changes)
190+
foreach (var (path, kind) in changes)
191191
{
192192
if (_disposed || !_raiseEvents)
193193
{
@@ -197,11 +197,5 @@ private void NotifyChanges()
197197
OnFileChange?.Invoke(this, new ChangedPath(path, kind));
198198
}
199199
}
200-
201-
private readonly struct FileMeta(FileSystemInfo fileInfo, bool foundAgain)
202-
{
203-
public readonly FileSystemInfo FileInfo = fileInfo;
204-
public readonly bool FoundAgain = foundAgain;
205-
}
206200
}
207201
}

test/dotnet-watch.Tests/FileWatcherTests.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,16 @@ await TestOperation(
9494
}
9595

9696
[Theory]
97-
[SkipOnPlatform(TestPlatforms.AnyUnix, "https://github.com/dotnet/runtime/issues/116351")]
9897
[CombinatorialData]
9998
public async Task NewFileInNewDirectory(bool usePolling, bool nested)
10099
{
100+
if (!usePolling && !(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)))
101+
{
102+
// Skip test on Unix:
103+
// https://github.com/dotnet/runtime/issues/116351
104+
return;
105+
}
106+
101107
var dir = _testAssetManager.CreateTestDirectory(identifier: usePolling.ToString()).Path;
102108

103109
var dir1 = Path.Combine(dir, "dir1");

0 commit comments

Comments
 (0)