Skip to content

Commit

Permalink
feat(api): Add UserAtomicLock attribute and middleware
Browse files Browse the repository at this point in the history
This commit adds the `UserAtomicLock` attribute and middleware to the API. The `UserAtomicLock` attribute is used to lock concurrency on a given action to a specific user. The `UserAtomicLockMiddleware` provides the functionality to lock a given action to a specific user. This ensures that only one request from the same user can access the locked action at a time, preventing unwanted concurrent modifications.
  • Loading branch information
SakuraIsayeki committed May 10, 2024
1 parent 71630a3 commit 6cb8259
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 0 deletions.
10 changes: 10 additions & 0 deletions WowsKarma.Api/Infrastructure/Attributes/UserAtomicLockAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace WowsKarma.Api.Infrastructure.Attributes;

/// <summary>
/// Provides an attribute to lock concurrency on a given action to a given user.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class UserAtomicLockAttribute : Attribute
{
public UserAtomicLockAttribute() { }
}
64 changes: 64 additions & 0 deletions WowsKarma.Api/Middlewares/UserAtomicLockMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Concurrent;
using WowsKarma.Api.Infrastructure.Attributes;
using WowsKarma.Common;

namespace WowsKarma.Api.Middlewares;

/// <summary>
/// Provides a middleware to lock a given action to a given user.
/// </summary>
public class UserAtomicLockMiddleware : IMiddleware
{
private static readonly ConcurrentDictionary<Tuple<string, uint>, object> Locks = new();
private static object ConcurrencyLock = new();

/// <inheritdoc />
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
// Get the current user and endpoint
uint? uid = ctx.User.ToAccountListing()?.Id;
PathString path = ctx.Request.Path;

if (uid is null)
{
// Pass through.
await next(ctx);
return;
}

if (ctx.GetEndpoint()?.Metadata.GetMetadata<UserAtomicLockAttribute>() is null)
{
// Pass through.
await next(ctx);
return;
}

bool lockExists;
// lock (ConcurrencyLock)
// {
lockExists = Locks.TryGetValue(new(path, uid.Value), out _) || !Locks.TryAdd(new(path, uid.Value), new());
// }

// Get or try to add the lock object.
if (lockExists)
{
// Lock is already taken.
ctx.Response.StatusCode = StatusCodes.Status429TooManyRequests;
return;
}

// Hold the lock for the request's duration.
try
{
await next(ctx);
}
finally
{
// lock (ConcurrencyLock)
// {
// Release the lock.
Locks.TryRemove(new(path, uid.Value), out _);
// }
}
}
}

0 comments on commit 6cb8259

Please sign in to comment.