forked from BrighterCommand/Brighter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial suggestion for Global locking for Timed Services
Issue BrighterCommand#3075 This is an example of providers to allow locking across resources, included is - In Memory Lock (for when we only want a lock in process) - Azure Blob Lock
- Loading branch information
Showing
13 changed files
with
385 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
Paramore.Brighter.Locking.Azure/AzureBlobLockingProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
using Azure; | ||
using Azure.Storage.Blobs; | ||
using Azure.Storage.Blobs.Specialized; | ||
using Microsoft.Extensions.Logging; | ||
using Paramore.Brighter.Logging; | ||
|
||
namespace Paramore.Brighter.Locking.Azure; | ||
|
||
public class AzureBlobLockingProvider(AzureBlobLockingProviderOptions options) : IDistributedLock | ||
{ | ||
private readonly BlobContainerClient _containerClient = new BlobContainerClient(options.BlobContainerUri, options.TokenCredential); | ||
private readonly ILogger _logger = ApplicationLogging.CreateLogger<AzureBlobLockingProviderOptions>(); | ||
|
||
private readonly Dictionary<string, string> _leases = new Dictionary<string, string>(); | ||
|
||
public async Task<bool> ObtainLockAsync(string resource, CancellationToken cancellationToken) | ||
{ | ||
var client = GetBlobClient(resource); | ||
|
||
// Write if does not exist | ||
if (!await client.ExistsAsync(cancellationToken)) | ||
{ | ||
await using var emptyStream = new MemoryStream(); | ||
await using var writer = new StreamWriter(emptyStream); | ||
await writer.WriteAsync(string.Empty); | ||
await writer.FlushAsync(cancellationToken); | ||
emptyStream.Position = 0; | ||
await client.UploadAsync(emptyStream, cancellationToken: cancellationToken); | ||
} | ||
|
||
try | ||
{ | ||
var response = await client.GetBlobLeaseClient().AcquireAsync(options.LeaseValidity, cancellationToken: cancellationToken); | ||
_leases.Add(NormaliseResourceName(resource), response.Value.LeaseId); | ||
return true; | ||
} | ||
catch (RequestFailedException e) | ||
{ | ||
_logger.LogInformation("Could not Acquire Lease on Blob {LockResourceName}", resource); | ||
return false; | ||
} | ||
} | ||
|
||
public bool ObtainLock(string resource) | ||
{ | ||
var client = GetBlobClient(resource); | ||
|
||
// Write if does not exist | ||
if (!client.Exists()) | ||
{ | ||
using var emptyStream = new MemoryStream(); | ||
using var writer = new StreamWriter(emptyStream); | ||
writer.Write(string.Empty); | ||
writer.Flush(); | ||
emptyStream.Position = 0; | ||
client.Upload(emptyStream); | ||
} | ||
|
||
try | ||
{ | ||
var response = client.GetBlobLeaseClient().Acquire(options.LeaseValidity); | ||
_leases.Add(NormaliseResourceName(resource), response.Value.LeaseId); | ||
return true; | ||
} | ||
catch (RequestFailedException e) | ||
{ | ||
_logger.LogInformation("Could not Acquire Lease on Blob {LockResourceName}", resource); | ||
return false; | ||
} | ||
} | ||
|
||
public async Task ReleaseLockAsync(string resource, CancellationToken cancellationToken) | ||
{ | ||
var client = GetBlobLeaseClientForResource(resource); | ||
if(client == null) | ||
return; | ||
await client.ReleaseAsync(cancellationToken: cancellationToken); | ||
_leases.Remove(NormaliseResourceName(resource)); | ||
} | ||
|
||
public void ReleaseLock(string resource) | ||
{ | ||
var client = GetBlobLeaseClientForResource(resource); | ||
if(client == null) | ||
return; | ||
client.Release(); | ||
_leases.Remove(NormaliseResourceName(resource)); | ||
} | ||
|
||
private BlobLeaseClient? GetBlobLeaseClientForResource(string resource) | ||
{ | ||
if (_leases.ContainsKey(NormaliseResourceName(resource))) | ||
return GetBlobClient(resource).GetBlobLeaseClient(_leases[NormaliseResourceName(resource)]); | ||
|
||
_logger.LogInformation("No lock found for {LockResourceName}", resource); | ||
return null; | ||
} | ||
|
||
private BlobClient GetBlobClient(string resource) | ||
{ | ||
var storageLocation = options.StorageLocationFunc.Invoke(NormaliseResourceName(resource)); | ||
return _containerClient.GetBlobClient(storageLocation); | ||
} | ||
|
||
private static string NormaliseResourceName(string resourceName) => resourceName.ToLower(); | ||
} |
29 changes: 29 additions & 0 deletions
29
Paramore.Brighter.Locking.Azure/AzureBlobLockingProviderOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using Azure.Core; | ||
|
||
namespace Paramore.Brighter.Locking.Azure; | ||
|
||
public class AzureBlobLockingProviderOptions( | ||
Uri blobContainerUri, | ||
TokenCredential tokenCredential | ||
) | ||
{ | ||
/// <summary> | ||
/// The URI of the blob container | ||
/// </summary> | ||
public Uri BlobContainerUri { get; init; } = blobContainerUri; | ||
|
||
/// <summary> | ||
/// The Credential to use when writing blobs | ||
/// </summary> | ||
public TokenCredential TokenCredential { get; init; } = tokenCredential; | ||
|
||
/// <summary> | ||
/// The amount of time before the lease automatically expires | ||
/// </summary> | ||
public TimeSpan LeaseValidity { get; init; } = TimeSpan.FromMinutes(1); | ||
|
||
/// <summary> | ||
/// The function to provide the location to store the locks inside of the Blob container | ||
/// </summary> | ||
public Func<string, string> StorageLocationFunc = (resource) => $"lock-{resource}"; | ||
} |
19 changes: 19 additions & 0 deletions
19
Paramore.Brighter.Locking.Azure/Paramore.Brighter.Locking.Azure.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<Title>This is the Azure Distributed Locking Provider.</Title> | ||
<Authors>Paul Reardon</Authors> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\src\Paramore.Brighter\Paramore.Brighter.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Azure.Storage.Blobs" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Paramore.Brighter; | ||
|
||
public interface IDistributedLock | ||
{ | ||
/// <summary> | ||
/// Attempt to obtain a lock on a resource | ||
/// </summary> | ||
/// <param name="resource">The name of the resource to Lock</param> | ||
/// <param name="cancellationToken">The Cancellation Token</param> | ||
/// <returns>True if the lock was obtained</returns> | ||
Task<bool> ObtainLockAsync(string resource, CancellationToken cancellationToken); | ||
|
||
/// <summary> | ||
/// Attempt to obtain a lock on a resource | ||
/// </summary> | ||
/// <param name="resource">The name of the resource to Lock</param> | ||
/// <returns>True if the lock was obtained</returns> | ||
bool ObtainLock(string resource); | ||
|
||
/// <summary> | ||
/// Release a lock | ||
/// </summary> | ||
/// <param name="resource"></param> | ||
/// <param name="cancellationToken"></param> | ||
/// <returns>Awaitable Task</returns> | ||
Task ReleaseLockAsync(string resource, CancellationToken cancellationToken); | ||
|
||
/// <summary> | ||
/// Release a lock | ||
/// </summary> | ||
/// <param name="resource"></param> | ||
/// <returns>Awaitable Task</returns> | ||
void ReleaseLock(string resource); | ||
} |
Oops, something went wrong.