feat: support concurrency in json file storage provider
This commit is contained in:
+35
-2
@@ -4,6 +4,7 @@ namespace StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
|||||||
|
|
||||||
public sealed class JsonFileStorageProvider(JsonStorageOptions options)
|
public sealed class JsonFileStorageProvider(JsonStorageOptions options)
|
||||||
{
|
{
|
||||||
|
private static readonly SemaphoreSlim FileLock = new(1, 1);
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
@@ -13,7 +14,7 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options)
|
|||||||
|
|
||||||
public async Task<string> ReadAsync(string key, CancellationToken ct = default)
|
public async Task<string> ReadAsync(string key, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var data = await LoadAsync(ct);
|
var data = await LoadWithLockAsync(ct);
|
||||||
|
|
||||||
if (data is not null && data.TryGetValue(key, out var v))
|
if (data is not null && data.TryGetValue(key, out var v))
|
||||||
{
|
{
|
||||||
@@ -25,23 +26,55 @@ public sealed class JsonFileStorageProvider(JsonStorageOptions options)
|
|||||||
|
|
||||||
public Task<Dictionary<string, string>> ReadAllAsync(CancellationToken ct = default)
|
public Task<Dictionary<string, string>> ReadAllAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return LoadAsync(ct);
|
return LoadWithLockAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteAsync(string key, string encryptedData, CancellationToken ct = default)
|
public async Task WriteAsync(string key, string encryptedData, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await FileLock.WaitAsync(ct);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var data = await LoadAsync(ct);
|
var data = await LoadAsync(ct);
|
||||||
data[key] = encryptedData;
|
data[key] = encryptedData;
|
||||||
await SaveAsync(data, ct);
|
await SaveAsync(data, ct);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FileLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteAsync(string key, CancellationToken ct = default)
|
public async Task<bool> DeleteAsync(string key, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await FileLock.WaitAsync(ct);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var data = await LoadAsync(ct);
|
var data = await LoadAsync(ct);
|
||||||
var result = data.Remove(key);
|
var result = data.Remove(key);
|
||||||
await SaveAsync(data, ct);
|
await SaveAsync(data, ct);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FileLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, string>> LoadWithLockAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
await FileLock.WaitAsync(ct);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await LoadAsync(ct);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FileLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Dictionary<string, string>> LoadAsync(CancellationToken ct)
|
private async Task<Dictionary<string, string>> LoadAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
|||||||
+19
@@ -79,4 +79,23 @@ public class JsonFileStorageProviderTests : IDisposable
|
|||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_WhenCalledConcurrently_ItShouldNotThrowFileInUseException()
|
||||||
|
{
|
||||||
|
const int numberOfWrites = 50;
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (var index in Enumerable.Range(0, numberOfWrites))
|
||||||
|
{
|
||||||
|
tasks.Add(Task.Run(() => _sut.WriteAsync($"Key{index}", $"Val{index}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
var act = async () => await Task.WhenAll(tasks);
|
||||||
|
|
||||||
|
await act.Should().NotThrowAsync();
|
||||||
|
|
||||||
|
var allData = await _sut.ReadAllAsync();
|
||||||
|
allData.Should().HaveCount(numberOfWrites);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user