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 for v9
issue BrighterCommand#3075
- Loading branch information
Showing
13 changed files
with
410 additions
and
30 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}"; | ||
} |
17 changes: 17 additions & 0 deletions
17
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,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</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
Oops, something went wrong.