Skip to content

Commit e059c40

Browse files
committed
fix: Coalesce file system watcher reloads.
1 parent 33dc6c5 commit e059c40

File tree

1 file changed

+47
-28
lines changed

1 file changed

+47
-28
lines changed

pkgs/sdk/server/src/Internal/DataSources/FileDataSource.cs

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.IO;
@@ -23,6 +23,7 @@ internal sealed class FileDataSource : IDataSource
2323
private readonly FileDataTypes.IFileReader _fileReader;
2424
private readonly bool _skipMissingPaths;
2525
private readonly Logger _logger;
26+
private readonly object _loadLock = new object();
2627
private volatile bool _started;
2728
private volatile bool _loadedValidData;
2829
private volatile int _lastVersion;
@@ -88,35 +89,38 @@ private void Dispose(bool disposing)
8889

8990
private void LoadAll()
9091
{
91-
var version = Interlocked.Increment(ref _lastVersion);
92-
var flags = new Dictionary<string, ItemDescriptor>();
93-
var segments = new Dictionary<string, ItemDescriptor>();
94-
foreach (var path in _paths)
92+
lock (_loadLock)
9593
{
96-
try
97-
{
98-
var content = _fileReader.ReadAllText(path);
99-
_logger.Debug("file data: {0}", content);
100-
var data = _parser.Parse(content, version);
101-
_dataMerger.AddToData(data, flags, segments);
102-
}
103-
catch (FileNotFoundException) when (_skipMissingPaths)
94+
var version = Interlocked.Increment(ref _lastVersion);
95+
var flags = new Dictionary<string, ItemDescriptor>();
96+
var segments = new Dictionary<string, ItemDescriptor>();
97+
foreach (var path in _paths)
10498
{
105-
_logger.Debug("{0}: {1}", path, "File not found");
106-
}
107-
catch (Exception e)
108-
{
109-
LogHelpers.LogException(_logger, "Failed to load " + path, e);
110-
return;
99+
try
100+
{
101+
var content = _fileReader.ReadAllText(path);
102+
_logger.Debug("file data: {0}", content);
103+
var data = _parser.Parse(content, version);
104+
_dataMerger.AddToData(data, flags, segments);
105+
}
106+
catch (FileNotFoundException) when (_skipMissingPaths)
107+
{
108+
_logger.Debug("{0}: {1}", path, "File not found");
109+
}
110+
catch (Exception e)
111+
{
112+
LogHelpers.LogException(_logger, "Failed to load " + path, e);
113+
return;
114+
}
111115
}
116+
var allData = new FullDataSet<ItemDescriptor>(
117+
ImmutableDictionary.Create<DataKind, KeyedItems<ItemDescriptor>>()
118+
.SetItem(DataModel.Features, new KeyedItems<ItemDescriptor>(flags))
119+
.SetItem(DataModel.Segments, new KeyedItems<ItemDescriptor>(segments))
120+
);
121+
_dataSourceUpdates.Init(allData);
122+
_loadedValidData = true;
112123
}
113-
var allData = new FullDataSet<ItemDescriptor>(
114-
ImmutableDictionary.Create<DataKind, KeyedItems<ItemDescriptor>>()
115-
.SetItem(DataModel.Features, new KeyedItems<ItemDescriptor>(flags))
116-
.SetItem(DataModel.Segments, new KeyedItems<ItemDescriptor>(segments))
117-
);
118-
_dataSourceUpdates.Init(allData);
119-
_loadedValidData = true;
120124
}
121125

122126
private void TriggerReload()
@@ -183,10 +187,15 @@ internal sealed class FileWatchingReloader : IDisposable
183187
private readonly ISet<string> _filePaths;
184188
private readonly Action _reload;
185189
private readonly List<FileSystemWatcher> _watchers;
190+
private readonly Timer _debounceTimer;
191+
private readonly int _debounceMillis;
192+
private readonly object _timerLock = new object();
186193

187-
public FileWatchingReloader(List<string> paths, Action reload)
194+
public FileWatchingReloader(List<string> paths, Action reload, int debounceMillis = 100)
188195
{
189196
_reload = reload;
197+
_debounceMillis = debounceMillis;
198+
_debounceTimer = new Timer(OnDebounceTimerElapsed, null, Timeout.Infinite, Timeout.Infinite);
190199

191200
_filePaths = new HashSet<string>();
192201
var dirPaths = new HashSet<string>();
@@ -216,10 +225,19 @@ private void ChangedPath(string path)
216225
{
217226
if (_filePaths.Contains(path))
218227
{
219-
_reload();
228+
lock (_timerLock)
229+
{
230+
// Reset the timer to debounce multiple rapid file changes
231+
_debounceTimer.Change(_debounceMillis, Timeout.Infinite);
232+
}
220233
}
221234
}
222235

236+
private void OnDebounceTimerElapsed(object state)
237+
{
238+
_reload();
239+
}
240+
223241
public void Dispose()
224242
{
225243
Dispose(true);
@@ -229,6 +247,7 @@ private void Dispose(bool disposing)
229247
{
230248
if (disposing)
231249
{
250+
_debounceTimer?.Dispose();
232251
foreach (var w in _watchers)
233252
{
234253
w.Dispose();

0 commit comments

Comments
 (0)