Skip to content

Commit d1f90f1

Browse files
committed
fix: Retry after partial file reads.
1 parent faf51dc commit d1f90f1

1 file changed

Lines changed: 38 additions & 0 deletions

File tree

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ internal sealed class FileDataSource : IDataSource
2828
private volatile int _lastVersion;
2929
private object _updateLock = new object();
3030

31+
private const int MaxRetries = 5;
32+
private readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(0.6);
33+
private readonly Dictionary<string, int> _retryCounts = new Dictionary<string, int>();
34+
3135
public FileDataSource(IDataSourceUpdates dataSourceUpdates, FileDataTypes.IFileReader fileReader,
3236
List<string> paths, bool autoUpdate, Func<string, object> alternateParser, bool skipMissingPaths,
3337
FileDataTypes.DuplicateKeysHandling duplicateKeysHandling,
@@ -102,17 +106,51 @@ private void LoadAll()
102106
_logger.Debug("file data: {0}", content);
103107
var data = _parser.Parse(content, version);
104108
_dataMerger.AddToData(data, flags, segments);
109+
// Remove any retry count associated with this path.
110+
_retryCounts.Remove(path);
105111
}
106112
catch (FileNotFoundException) when (_skipMissingPaths)
107113
{
108114
_logger.Debug("{0}: {1}", path, "File not found");
109115
}
116+
catch (System.Text.Json.JsonException)
117+
{
118+
// We may have received the notification of a file change while the file was being written.
119+
// So we may read an empty or partially written file. So, when we encounter a JSON parsing issue
120+
// we will retry after a short delay.
121+
// We will retry up to MaxRetries times before giving up.
122+
if (!_retryCounts.ContainsKey(path))
123+
{
124+
_retryCounts[path] = 0;
125+
}
126+
_retryCounts[path]++;
127+
128+
if (_retryCounts[path] < MaxRetries)
129+
{
130+
_logger.Warn("{0}: {1}", path, "Failed to parse file, retrying in " + RetryDelay.TotalMilliseconds + " milliseconds");
131+
Task.Run(async () =>
132+
{
133+
await Task.Delay(RetryDelay);
134+
LoadAll();
135+
});
136+
}
137+
else
138+
{
139+
_logger.Error("{0}: {1}", path, "Failed to parse file after " + MaxRetries + " retries");
140+
}
141+
142+
return;
143+
}
110144
catch (Exception e)
111145
{
112146
LogHelpers.LogException(_logger, "Failed to load " + path, e);
113147
return;
114148
}
115149
}
150+
151+
// If any files failed to load, from anything other than not existing, then that
152+
// update would fail. This behavior is retained with the addition of the retry. But it should be
153+
// examined.
116154

117155
var allData = new FullDataSet<ItemDescriptor>(
118156
ImmutableDictionary.Create<DataKind, KeyedItems<ItemDescriptor>>()

0 commit comments

Comments
 (0)