From 0928c19b0603686a646e41d4c138652556301f92 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sat, 24 Feb 2024 19:14:08 +0100 Subject: [PATCH] [backend/core] Add keyed async locks in UserResolver to prevent insertion conflicts --- .../Federation/ActivityPub/UserResolver.cs | 14 ++++- .../Core/Services/UserService.cs | 20 ++++--- Iceshrimp.Backend/Iceshrimp.Backend.csproj | 55 ++++++++++--------- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/UserResolver.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/UserResolver.cs index 301aa377..087c43f0 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityPub/UserResolver.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/UserResolver.cs @@ -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 KeyedLocker = new(o => + { + o.PoolSize = 100; + o.PoolInitialFill = 5; + }); + /* * The full web finger algorithm: * @@ -119,8 +126,11 @@ public class UserResolver( if (user != null) return await GetUpdatedUser(user); - // Pass the job on to userSvc, which will create the user - return await userSvc.CreateUserAsync(uri, acct); + 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 GetUpdatedUser(User user) diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs index e1abe799..218dbbd4 100644 --- a/Iceshrimp.Backend/Core/Services/UserService.cs +++ b/Iceshrimp.Backend/Core/Services/UserService.cs @@ -66,17 +66,10 @@ public class UserService( public async Task 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(), diff --git a/Iceshrimp.Backend/Iceshrimp.Backend.csproj b/Iceshrimp.Backend/Iceshrimp.Backend.csproj index 8697ded8..36a050c3 100644 --- a/Iceshrimp.Backend/Iceshrimp.Backend.csproj +++ b/Iceshrimp.Backend/Iceshrimp.Backend.csproj @@ -15,46 +15,47 @@ - - - - - - - - - - - - + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + + + + + + + - - - - + + + + - + - +