Skip to content

Commit 1efa70d

Browse files
authored
Added distributed lock (#3)
* Added DistributedLock * Refactor * Added settings for DistributedLock * Updated README
1 parent 44f6e62 commit 1efa70d

21 files changed

+354
-16
lines changed

Locks.sln

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{1E0E
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.MemoryLock", "samples\Samples.MemoryLock\Samples.MemoryLock.csproj", "{C2BAB68A-9C7C-41E4-BE9F-A1C457AEE1AC}"
1717
EndProject
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Locks.SqlServer", "source\Locks.SqlServer\Locks.SqlServer.csproj", "{58D1DD82-AD81-484B-B508-2CB15D91C534}"
19+
EndProject
1820
Global
1921
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2022
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
3335
{C2BAB68A-9C7C-41E4-BE9F-A1C457AEE1AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
3436
{C2BAB68A-9C7C-41E4-BE9F-A1C457AEE1AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
3537
{C2BAB68A-9C7C-41E4-BE9F-A1C457AEE1AC}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{58D1DD82-AD81-484B-B508-2CB15D91C534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{58D1DD82-AD81-484B-B508-2CB15D91C534}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{58D1DD82-AD81-484B-B508-2CB15D91C534}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{58D1DD82-AD81-484B-B508-2CB15D91C534}.Release|Any CPU.Build.0 = Release|Any CPU
3642
EndGlobalSection
3743
GlobalSection(SolutionProperties) = preSolution
3844
HideSolutionNode = FALSE
@@ -41,6 +47,7 @@ Global
4147
{6F5A38A8-D6E1-4D1D-99B4-85CE7C40F468} = {1E772EE5-484B-4803-9FA1-6681DC601553}
4248
{4942FB20-6E79-4AA6-B607-728F67291811} = {8AB8A443-B3CA-49F8-9BD6-6476AA7F9EEC}
4349
{C2BAB68A-9C7C-41E4-BE9F-A1C457AEE1AC} = {1E0E5F59-5328-457A-B420-5E104EDFA528}
50+
{58D1DD82-AD81-484B-B508-2CB15D91C534} = {1E772EE5-484B-4803-9FA1-6681DC601553}
4451
EndGlobalSection
4552
GlobalSection(ExtensibilityGlobals) = postSolution
4653
SolutionGuid = {3E2C15DA-E3F4-407A-9BD5-A87A4D07597C}

README.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,19 @@
2020

2121

2222
### Usage
23+
#### MemoryLock
24+
```C#
25+
using (await memoryLock.AcquireAsync("YOUR_KEY"))
26+
{
27+
// Shared resource (multi-threaded environment)
28+
}
29+
```
2330

31+
#### DistributedLock
2432
```C#
25-
using (memoryLock.Acquire("YOUR_KEY"))
33+
await using (await distributedLock.AcquireAsync("YOUR_KEY"))
2634
{
27-
// Shared resource
35+
// Shared resource (distributed environment)
2836
}
2937
```
3038

samples/Samples.MemoryLock/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
{
1313
var memoryLock = scope.ServiceProvider.GetRequiredService<IMemoryLock>();
1414

15-
using (memoryLock.Acquire("YOUR_KEY"))
15+
using (memoryLock.AcquireAsync("YOUR_KEY"))
1616
{
1717
Console.WriteLine("YOUR_LOGIC");
1818
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Locks.Configurators;
2+
3+
namespace Locks.SqlServer.Extensions
4+
{
5+
public static class ExntesionUseSqlServer
6+
{
7+
public static void UseSqlServer(
8+
this IDistributedLockStorageConfigurator configurator)
9+
{
10+
var services = configurator.Services;
11+
}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Locks.Internals.Distributed.Storage;
4+
5+
namespace Locks.SqlServer.Internals
6+
{
7+
internal sealed class SqlServerDistributedLockRepository : IDistributedLockRepository
8+
{
9+
public Task AddFirstLock(DistributedLockStorageModel @lock)
10+
{
11+
throw new NotImplementedException();
12+
}
13+
14+
public Task<bool> Release(DistributedLockStorageModel @lock, DateTime nowUtc)
15+
{
16+
throw new NotImplementedException();
17+
}
18+
19+
public Task<bool> TryAcquire(string key, DateTime nowUtc, DateTime newExpirationUtc)
20+
{
21+
throw new NotImplementedException();
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\Locks\Locks.csproj" />
9+
</ItemGroup>
10+
11+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace Locks.Configurators
4+
{
5+
public interface IDistributedLockStorageConfigurator
6+
{
7+
IServiceCollection Services { get; }
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Locks.Configurators;
4+
using Locks.Internals.Distributed;
5+
using Locks.Internals.Distributed.Configurators;
6+
using Locks.Settings;
7+
8+
namespace Locks
9+
{
10+
public static class ExtensionAddDistributedLock
11+
{
12+
public static IServiceCollection AddDistributedLock(
13+
this IServiceCollection services,
14+
Action<IDistributedLockStorageConfigurator> storageConfiguration,
15+
Action<DistributedLockSettings> settingsConfiguration = null)
16+
{
17+
var storage = new DistributedLockStorageConfigurator(services);
18+
var settings = new DistributedLockSettings();
19+
20+
storageConfiguration(storage);
21+
22+
if (settingsConfiguration != null)
23+
{
24+
settingsConfiguration(settings);
25+
}
26+
27+
services.AddSingleton<IDistributedLockSettings>(settings);
28+
services.AddSingleton<IDistributedLock, DistributedLock>();
29+
30+
return services;
31+
}
32+
}
33+
}

source/Locks/IDistributedLock.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading.Tasks;
2+
using System.Threading;
3+
4+
namespace Locks
5+
{
6+
public interface IDistributedLock
7+
{
8+
Task<IDistributedLockInstance> AcquireAsync(string key, CancellationToken cancellationToken = default);
9+
}
10+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using System;
2+
3+
namespace Locks
4+
{
5+
public interface IDistributedLockInstance : IAsyncDisposable { }
6+
}

source/Locks/IMemoryLock.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ namespace Locks
55
{
66
public interface IMemoryLock
77
{
8-
Task<IMemoryLockInstance> Acquire(string key, CancellationToken cancellationToken = default);
8+
Task<IMemoryLockInstance> AcquireAsync(string key, CancellationToken cancellationToken = default);
99
}
1010
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Locks.Configurators;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace Locks.Internals.Distributed.Configurators
5+
{
6+
internal sealed class DistributedLockStorageConfigurator : IDistributedLockStorageConfigurator
7+
{
8+
public IServiceCollection Services { get; }
9+
10+
internal DistributedLockStorageConfigurator(IServiceCollection services)
11+
{
12+
Services = services;
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using Locks.Internals.Distributed.Storage;
2+
using Locks.Internals.Memory;
3+
using System;
4+
using System.Collections.Concurrent;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Locks.Internals.Distributed
9+
{
10+
internal sealed class DistributedLock : IDistributedLock
11+
{
12+
private static ConcurrentDictionary<string, bool> _isKeyAdded = new ConcurrentDictionary<string, bool>();
13+
14+
private readonly IDistributedLockRepository _repo;
15+
16+
private readonly IDistributedLockSettings _settings;
17+
18+
public DistributedLock(
19+
IDistributedLockRepository repo,
20+
IDistributedLockSettings settings)
21+
{
22+
_repo = repo;
23+
_settings = settings;
24+
}
25+
26+
public async Task<IDistributedLockInstance> AcquireAsync(
27+
string key,
28+
CancellationToken cancellationToken = default)
29+
{
30+
IMemoryLockInstance memoryLockInstance = null;
31+
32+
try
33+
{
34+
var isAddedFirstTime = false;
35+
36+
_ = _isKeyAdded.GetOrAdd(key, x =>
37+
{
38+
isAddedFirstTime = true;
39+
40+
return isAddedFirstTime;
41+
});
42+
43+
if (isAddedFirstTime)
44+
{
45+
await AddFirstLock(key).ConfigureAwait(false);
46+
}
47+
48+
memoryLockInstance = await new MemoryLock()
49+
.AcquireAsync(key, cancellationToken)
50+
.ConfigureAwait(false);
51+
52+
bool isAcquired = false;
53+
54+
DateTime expirationUtc;
55+
56+
do
57+
{
58+
cancellationToken.ThrowIfCancellationRequested();
59+
60+
var nowUtc = DateTime.UtcNow;
61+
62+
expirationUtc = nowUtc + _settings.LockTimeout;
63+
64+
isAcquired = await _repo.TryAcquire(key, nowUtc, expirationUtc).ConfigureAwait(false);
65+
66+
if (!isAcquired)
67+
{
68+
await Task.Delay(_settings.CheckingIntervalWhenLockIsNotReleased, cancellationToken).ConfigureAwait(false);
69+
}
70+
}
71+
while (!isAcquired);
72+
73+
cancellationToken.ThrowIfCancellationRequested();
74+
75+
var @lock = new DistributedLockStorageModel(key, expirationUtc);
76+
77+
return new DistributedLockInstance(memoryLockInstance, @lock, _repo);
78+
}
79+
catch
80+
{
81+
if (memoryLockInstance != null)
82+
{
83+
memoryLockInstance.Dispose();
84+
}
85+
86+
throw;
87+
}
88+
}
89+
90+
private async Task AddFirstLock(string key)
91+
{
92+
var nowUtc = DateTime.UtcNow;
93+
94+
var @lock = new DistributedLockStorageModel(key, nowUtc);
95+
96+
await _repo.AddFirstLock(@lock).ConfigureAwait(false);
97+
}
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Locks.Internals.Distributed.Storage;
2+
using System;
3+
using System.Threading.Tasks;
4+
5+
namespace Locks.Internals.Distributed
6+
{
7+
internal sealed class DistributedLockInstance : IDistributedLockInstance
8+
{
9+
private readonly IMemoryLockInstance _memoryLockInstance;
10+
11+
private readonly DistributedLockStorageModel _distributedLockStorageModel;
12+
13+
private readonly IDistributedLockRepository _repo;
14+
15+
internal DistributedLockInstance(
16+
IMemoryLockInstance memoryLockInstance,
17+
DistributedLockStorageModel distributedLockStorageModel,
18+
IDistributedLockRepository repo)
19+
{
20+
_memoryLockInstance = memoryLockInstance;
21+
_distributedLockStorageModel = distributedLockStorageModel;
22+
_repo = repo;
23+
}
24+
25+
public async ValueTask DisposeAsync()
26+
{
27+
try
28+
{
29+
var nowUtc = DateTime.UtcNow;
30+
31+
var x = await _repo.Release(_distributedLockStorageModel, nowUtc).ConfigureAwait(false);
32+
33+
//TODO Log Warning (x)
34+
}
35+
finally
36+
{
37+
if (_memoryLockInstance != null)
38+
{
39+
_memoryLockInstance.Dispose();
40+
}
41+
}
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace Locks.Internals.Distributed
4+
{
5+
internal interface IDistributedLockSettings
6+
{
7+
TimeSpan LockTimeout { get; }
8+
9+
TimeSpan CheckingIntervalWhenLockIsNotReleased { get; }
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Locks.Internals.Distributed.Storage
4+
{
5+
internal sealed class DistributedLockStorageModel
6+
{
7+
public string Key { get; }
8+
9+
public DateTime ExpirationUtc { get; }
10+
11+
internal DistributedLockStorageModel(string key, DateTime expirationUtc)
12+
{
13+
Key = key;
14+
ExpirationUtc = expirationUtc;
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace Locks.Internals.Distributed.Storage
5+
{
6+
internal interface IDistributedLockRepository
7+
{
8+
Task AddFirstLock(DistributedLockStorageModel @lock);
9+
10+
Task<bool> TryAcquire(string key, DateTime nowUtc, DateTime newExpirationUtc);
11+
12+
Task<bool> Release(DistributedLockStorageModel @lock, DateTime nowUtc);
13+
}
14+
}

source/Locks/Internals/Memory/MemoryLock.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ internal sealed class MemoryLock : IMemoryLock
88
{
99
private static ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
1010

11-
public async Task<IMemoryLockInstance> Acquire(string key, CancellationToken cancellationToken = default)
11+
public async Task<IMemoryLockInstance> AcquireAsync(string key, CancellationToken cancellationToken = default)
1212
{
1313
var @lock = _locks.GetOrAdd(key, x => new SemaphoreSlim(1, 1));
1414

0 commit comments

Comments
 (0)