[backend/federation] Fix race condition when updating a user during a request

This commit is contained in:
Laura Hausmann 2024-02-17 19:42:15 +01:00
parent 0884f462c0
commit f073018e95
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 29 additions and 16 deletions

View file

@ -26,7 +26,7 @@ public class DriveFile : IEntity
/// The created date of the DriveFile. /// The created date of the DriveFile.
/// </summary> /// </summary>
[Column("createdAt")] [Column("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; }
/// <summary> /// <summary>
/// The owner ID. /// The owner ID.
@ -188,7 +188,7 @@ public class DriveFile : IEntity
[Key] [Key]
[Column("id")] [Column("id")]
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = IdHelpers.GenerateSlowflakeId(); public string Id { get; set; } = null!;
public class FileProperties public class FileProperties
{ {

View file

@ -18,9 +18,7 @@ public class ActivityDeliverService(ILogger<ActivityDeliverService> logger, Queu
{ {
ActorId = actor.Id, ActorId = actor.Id,
RecipientIds = recipients.Select(p => p.Id).ToList(), RecipientIds = recipients.Select(p => p.Id).ToList(),
SerializedActivity = SerializedActivity = JsonConvert.SerializeObject(activity, LdHelpers.JsonSerializerSettings),
JsonConvert.SerializeObject(activity,
LdHelpers.JsonSerializerSettings),
DeliverToFollowers = true DeliverToFollowers = true
}); });
} }
@ -34,9 +32,7 @@ public class ActivityDeliverService(ILogger<ActivityDeliverService> logger, Queu
{ {
ActorId = actor.Id, ActorId = actor.Id,
RecipientIds = recipients.Select(p => p.Id).ToList(), RecipientIds = recipients.Select(p => p.Id).ToList(),
SerializedActivity = SerializedActivity = JsonConvert.SerializeObject(activity, LdHelpers.JsonSerializerSettings),
JsonConvert.SerializeObject(activity,
LdHelpers.JsonSerializerSettings),
DeliverToFollowers = false DeliverToFollowers = false
}); });
} }

View file

@ -112,13 +112,18 @@ public class UserResolver(
private async Task<User> GetUpdatedUser(User user) private async Task<User> GetUpdatedUser(User user)
{ {
if (!user.NeedsUpdate) return user; if (!user.NeedsUpdate) return user;
user.LastFetchedAt = DateTime.UtcNow; // Prevent multiple background tasks from being started
try try
{ {
var task = followupTaskSvc.ExecuteTask("UpdateUserAsync", async provider => var task = followupTaskSvc.ExecuteTask("UpdateUserAsync", async provider =>
{ {
// Get a fresh UserService instance in a new scope
var bgUserSvc = provider.GetRequiredService<UserService>(); var bgUserSvc = provider.GetRequiredService<UserService>();
await bgUserSvc.UpdateUserAsync(user);
// Use the id overload so it doesn't attempt to insert in the main thread's DbContext
var fetchedUser = await bgUserSvc.UpdateUserAsync(user.Id);
user = fetchedUser;
}); });
// Return early, but continue execution in background // Return early, but continue execution in background

View file

@ -4,6 +4,7 @@ using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Federation.Cryptography; using Iceshrimp.Backend.Core.Federation.Cryptography;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Queues; using Iceshrimp.Backend.Core.Queues;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -240,6 +241,8 @@ public class DriveService(
file = new DriveFile file = new DriveFile
{ {
Id = IdHelpers.GenerateSlowflakeId(),
CreatedAt = DateTime.UtcNow,
User = user, User = user,
UserHost = user.Host, UserHost = user.Host,
Sha256 = digest, Sha256 = digest,
@ -332,6 +335,8 @@ file static class DriveFileExtensions
return new DriveFile return new DriveFile
{ {
Id = IdHelpers.GenerateSlowflakeId(),
CreatedAt = DateTime.UtcNow,
User = user, User = user,
Blurhash = file.Blurhash, Blurhash = file.Blurhash,
Type = file.Type, Type = file.Type,

View file

@ -158,9 +158,16 @@ public class UserService(
} }
} }
public async Task<User> UpdateUserAsync(User user, ASActor? actor = null) public async Task<User> UpdateUserAsync(string id)
{ {
if (!user.NeedsUpdate && actor == null) return user; var user = await db.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id) ??
throw new Exception("Cannot update nonexistent user");
return await UpdateUserAsync(user, force: true);
}
public async Task<User> UpdateUserAsync(User user, ASActor? actor = null, bool force = false)
{
if (!user.NeedsUpdate && actor == null && !force) return user;
if (actor is { IsUnresolved: true } or { Username: null }) if (actor is { IsUnresolved: true } or { Username: null })
actor = null; // This will trigger a fetch a couple lines down actor = null; // This will trigger a fetch a couple lines down