[backend/core] Add keyed async locks in UserResolver to prevent insertion conflicts

This commit is contained in:
Laura Hausmann 2024-02-24 19:14:08 +01:00
parent 99a1ff43f6
commit 0928c19b06
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 52 additions and 37 deletions

View file

@ -1,3 +1,4 @@
using AsyncKeyedLock;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Federation.WebFinger;
using Iceshrimp.Backend.Core.Middleware;
@ -12,6 +13,12 @@ public class UserResolver(
FollowupTaskService followupTaskSvc
)
{
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{
o.PoolSize = 100;
o.PoolInitialFill = 5;
});
/*
* The full web finger algorithm:
*
@ -119,9 +126,12 @@ public class UserResolver(
if (user != null)
return await GetUpdatedUser(user);
using (await KeyedLocker.LockAsync(uri))
{
// Pass the job on to userSvc, which will create the user
return await userSvc.CreateUserAsync(uri, acct);
}
}
private async Task<User> GetUpdatedUser(User user)
{

View file

@ -66,17 +66,10 @@ public class UserService(
public async Task<User> CreateUserAsync(string uri, string acct)
{
logger.LogDebug("Creating user {acct} with uri {uri}", acct, uri);
var actor = await fetchSvc.FetchActorAsync(uri);
logger.LogDebug("Got actor: {url}", actor.Url);
actor.Normalize(uri, acct);
if (actor.PublicKey?.Id == null || actor.PublicKey?.PublicKey == null)
throw new GracefulException(HttpStatusCode.UnprocessableEntity, "Actor has no valid public key");
var user = await db.Users
.IncludeCommonProperties()
.FirstOrDefaultAsync(p => p.Uri != null && p.Uri == actor.Id);
.FirstOrDefaultAsync(p => p.Uri != null && p.Uri == uri);
if (user != null)
{
@ -85,6 +78,17 @@ public class UserService(
return user;
}
var actor = await fetchSvc.FetchActorAsync(uri);
logger.LogDebug("Got actor: {url}", actor.Url);
actor.Normalize(uri, acct);
if (actor.Id != uri)
throw GracefulException.UnprocessableEntity("Uri doesn't match id of fetched actor");
if (actor.PublicKey?.Id == null || actor.PublicKey?.PublicKey == null)
throw GracefulException.UnprocessableEntity("Actor has no valid public key");
user = new User
{
Id = IdHelpers.GenerateSlowflakeId(),

View file

@ -18,6 +18,7 @@
<PackageReference Include="Amazon.S3" Version="0.31.2" />
<PackageReference Include="AngleSharp" Version="1.1.0" />
<PackageReference Include="Asp.Versioning.Http" Version="8.0.0" />
<PackageReference Include="AsyncKeyedLock" Version="6.3.4" />
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
<PackageReference Include="cuid.net" Version="5.0.2" />
<PackageReference Include="dotNetRdf.Core" Version="3.2.1-dev" />