Skip to content

Commit

Permalink
Handle files locked by another process, ensure logger is always wrapped
Browse files Browse the repository at this point in the history
  • Loading branch information
z4kn4fein committed Mar 31, 2022
1 parent a3122cd commit d66ab3d
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 36 deletions.
2 changes: 2 additions & 0 deletions src/ConfigCat.Client.Tests/ConfigCatClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public void TestInitialize()
loggerMock.Reset();
evaluatorMock.Reset();
configDeserializerMock.Reset();

loggerMock.Setup(l => l.LogLevel).Returns(LogLevel.Warning);
}

[ExpectedException(typeof(ArgumentException))]
Expand Down
4 changes: 2 additions & 2 deletions src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace ConfigCat.Client.Tests
{
public abstract class ConfigEvaluatorTestsBase
{
private readonly ILogger logger = new LoggerWrapper(new ConsoleLogger(LogLevel.Debug));
private readonly ILogger logger = new ConsoleLogger(LogLevel.Debug);

private protected readonly IDictionary<string, Setting> config;

Expand All @@ -23,7 +23,7 @@ public abstract class ConfigEvaluatorTestsBase

public ConfigEvaluatorTestsBase()
{
this.configEvaluator = new RolloutEvaluator(logger);
this.configEvaluator = new RolloutEvaluator(new LoggerWrapper(logger));

this.config = this.GetSampleJson().Deserialize<SettingsWithPreferences>().Settings;
}
Expand Down
6 changes: 3 additions & 3 deletions src/ConfigCatClient/ConfigCatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public ConfigCatClient(Action<ConfigCatClientOptions> configurationAction)

configuration.Validate();

this.log = configuration.Logger;
this.log = new LoggerWrapper(configuration.Logger);
this.configDeserializer = new ConfigDeserializer();
this.configEvaluator = new RolloutEvaluator(this.log);

Expand Down Expand Up @@ -151,12 +151,12 @@ public ConfigCatClient(Action<ConfigCatClientOptions> configurationAction)
}

/// <summary>
/// For test purpose only
/// For testing purposes only
/// </summary>
internal ConfigCatClient(IConfigService configService, ILogger logger, IRolloutEvaluator evaluator, IConfigDeserializer configDeserializer)
{
this.configService = configService;
this.log = logger;
this.log = new LoggerWrapper(logger);
this.configEvaluator = evaluator;
this.configDeserializer = configDeserializer;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ConfigCatClient/Configuration/ConfigurationBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace ConfigCat.Client
/// </summary>
public abstract class ConfigurationBase
{
private ILogger logger = LoggerWrapper.Default;
private ILogger logger = new ConsoleLogger(LogLevel.Warning);

/// <summary>
/// Logger instance.
Expand All @@ -19,7 +19,7 @@ public ILogger Logger
get => this.logger;
set
{
this.logger = new LoggerWrapper(value ?? throw new ArgumentNullException(nameof(Logger)));
this.logger = value ?? throw new ArgumentNullException(nameof(Logger));
}
}

Expand Down
9 changes: 5 additions & 4 deletions src/ConfigCatClient/Logging/LoggerWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ namespace ConfigCat.Client
{
internal sealed class LoggerWrapper : ILogger
{
public static readonly LoggerWrapper Default = new(new ConsoleLogger());

private readonly ILogger logger;

public LogLevel LogLevel { get; set; }
public LogLevel LogLevel
{
get => logger.LogLevel;
set => logger.LogLevel = value;
}

internal LoggerWrapper(ILogger logger)
{
this.LogLevel = logger.LogLevel;
this.logger = logger;
}

Expand Down
71 changes: 46 additions & 25 deletions src/ConfigCatClient/Override/LocalFileDataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ namespace ConfigCat.Client.Override
{
internal sealed class LocalFileDataSource : IOverrideDataSource
{
const int WAIT_TIME_FOR_UNLOCK = 200; // ms
const int MAX_WAIT_ITERATIONS = 50;

private int isReading;
private readonly string fullPath;
private readonly ILogger logger;
private readonly FileSystemWatcher fileSystemWatcher;
private readonly TaskCompletionSource<bool> asyncInit = new();
Expand All @@ -20,33 +24,33 @@ internal sealed class LocalFileDataSource : IOverrideDataSource

public LocalFileDataSource(string filePath, bool autoReload, ILogger logger)
{
this.fullPath = Path.GetFullPath(filePath);
if (autoReload)
{
var directory = Path.GetDirectoryName(filePath);
var directory = Path.GetDirectoryName(this.fullPath);
if (string.IsNullOrWhiteSpace(directory))
{
logger.Error($"Directory of {filePath} not found to watch.");
logger.Error($"Directory of {this.fullPath} not found to watch.");
}
else
{
this.fileSystemWatcher = new FileSystemWatcher(directory)
{
Filter = Path.GetFileName(filePath),
NotifyFilter = NotifyFilters.LastWrite,
EnableRaisingEvents = true
};
this.fileSystemWatcher.Changed += FileSystemWatcher_Changed;
logger.Information($"Watching {filePath} for changes.");
this.fileSystemWatcher = new FileSystemWatcher(directory);
this.fileSystemWatcher.Changed += OnChanged;
this.fileSystemWatcher.Created += OnChanged;
this.fileSystemWatcher.Renamed += OnChanged;
this.fileSystemWatcher.EnableRaisingEvents = true;
logger.Information($"Watching {this.fullPath} for changes.");
}
}

this.logger = logger;
_ = this.ReadFileAsync(filePath);
_ = this.ReadFileAsync(this.fullPath);
}

private async void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
private async void OnChanged(object sender, FileSystemEventArgs e)
{
if (e.ChangeType != WatcherChangeTypes.Changed)
// filter out events on temporary files
if (e.FullPath != this.fullPath)
return;

this.logger.Information($"Reload file {e.FullPath}.");
Expand Down Expand Up @@ -74,18 +78,34 @@ private async Task ReadFileAsync(string filePath)

try
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
var simplified = content.DeserializeOrDefault<SimplifiedConfig>();
if (simplified?.Entries != null)
for (int i = 1; i <= MAX_WAIT_ITERATIONS; i++)
{
this.overrideValues = simplified.Entries.ToDictionary(kv => kv.Key, kv => kv.Value.ToSetting());
return;
}
try
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
var simplified = content.DeserializeOrDefault<SimplifiedConfig>();
if (simplified?.Entries != null)
{
this.overrideValues = simplified.Entries.ToDictionary(kv => kv.Key, kv => kv.Value.ToSetting());
return;
}

var deserialized = content.Deserialize<SettingsWithPreferences>();
this.overrideValues = deserialized.Settings;
var deserialized = content.Deserialize<SettingsWithPreferences>();
this.overrideValues = deserialized.Settings;
break;
}
// this logic ensures that we keep trying to open the file for max 10s
// when it's locked by another process.
catch (IOException e) when (e.HResult == -2147024864) // ERROR_SHARING_VIOLATION
{
if (i >= MAX_WAIT_ITERATIONS)
throw;

await Task.Delay(WAIT_TIME_FOR_UNLOCK);
}
}
}
catch (Exception ex)
{
Expand All @@ -108,10 +128,11 @@ public void Dispose()
{
if (this.fileSystemWatcher != null)
{
this.fileSystemWatcher.Changed -= FileSystemWatcher_Changed;
this.fileSystemWatcher.Changed -= OnChanged;
this.fileSystemWatcher.Created -= OnChanged;
this.fileSystemWatcher.Renamed -= OnChanged;
this.fileSystemWatcher.Dispose();
}

this.syncInit.Dispose();
}

Expand Down

0 comments on commit d66ab3d

Please sign in to comment.