The single serial DeliveryWorker is replaced by Infrastructure/Jobs: - Job rows are leased with one FindOneAndUpdate (oldest RunAt first, a two-minute lease) and a reaper returns expired leases every 30 s; - enqueueing wakes the workers, which otherwise poll every five seconds; - delivery runs eight at a time with at most two per host, so a slow or dead server holds two slots, not the queue; - a failure waits n^4 + 15 + jitter seconds (Mastodon's curve) for up to 16 attempts; a 4xx other than 408/429 is final, a 429 honours Retry-After; - RemoteInstance is a per-host circuit breaker: ten consecutive failures quarantine a host for an hour, doubling to a week, and its jobs wait without spending attempts; - a delivery is queued once per activity and inbox (unique DedupeKey), and finished jobs expire after seven days (TTL on FinishedAt). Migration _004 moves pending Delivery rows into jobs and marks them abandoned, so a rollback to the old worker cannot send them twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
64 lines
2.8 KiB
C#
64 lines
2.8 KiB
C#
using MongoDB.Driver;
|
|
using MongoDB.Entities;
|
|
|
|
using PrivaPub.Federation.Actors;
|
|
using PrivaPub.Infrastructure.Data;
|
|
using PrivaPub.Infrastructure.Data.Migrations;
|
|
using PrivaPub.Models;
|
|
using PrivaPub.Models.Federation;
|
|
using PrivaPub.Models.User;
|
|
using PrivaPub.StaticServices;
|
|
using PrivaPub.Tests.Support;
|
|
|
|
namespace PrivaPub.Tests.Infrastructure
|
|
{
|
|
[Trait("Category", "Integration")]
|
|
public class IndexTests
|
|
{
|
|
[Fact]
|
|
public async Task Duplicates_are_removed_and_the_unique_indexes_then_hold()
|
|
{
|
|
Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip);
|
|
var token = TestContext.Current.CancellationToken;
|
|
await DB.Default.Index<ForeignAvatar>().DropAllAsync(token);
|
|
await DB.Default.Index<Follower>().DropAllAsync(token);
|
|
var actorUri = $"https://r.example/users/{Guid.NewGuid():N}";
|
|
var localId = Guid.NewGuid().ToString("N")[..24];
|
|
await DB.Default.SaveAsync(new[]
|
|
{
|
|
new ForeignAvatar { ActorURI = actorUri, Name = "old", UpdatedAt = DateTime.UtcNow.AddDays(-2) },
|
|
new ForeignAvatar { ActorURI = actorUri, Name = "new", UpdatedAt = DateTime.UtcNow }
|
|
}, token);
|
|
await DB.Default.SaveAsync(new[]
|
|
{
|
|
new Follower { LocalActorId = localId, ActorURI = actorUri, IsAccepted = false },
|
|
new Follower { LocalActorId = localId, ActorURI = actorUri, IsAccepted = true }
|
|
}, token);
|
|
|
|
await new _001_deduplicate_before_unique_indexes().UpgradeAsync();
|
|
await Indexes.Create(token);
|
|
|
|
Assert.Equal("new", (await DB.Default.Find<ForeignAvatar>().Match(a => a.ActorURI == actorUri).ExecuteSingleAsync(token)).Name);
|
|
Assert.True((await DB.Default.Find<Follower>().Match(f => f.LocalActorId == localId).ExecuteSingleAsync(token)).IsAccepted);
|
|
var duplicate = await Assert.ThrowsAsync<MongoWriteException>(async () =>
|
|
await DB.Default.SaveAsync(new ForeignAvatar { ActorURI = actorUri }, token));
|
|
Assert.Equal(ServerErrorCategory.DuplicateKey, duplicate.WriteError.Category);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_username_is_reserved_once_across_personas_and_groups()
|
|
{
|
|
Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip);
|
|
var token = TestContext.Current.CancellationToken;
|
|
var local = new LocalActorService(new DbEntities(), new StaticOptions<AppConfiguration>(new AppConfiguration { BackendBaseAddress = "https://privapub.test" }));
|
|
var name = $"n{Guid.NewGuid():N}"[..20];
|
|
|
|
Assert.True(await local.TryReserveUserName(name, LocalActorKind.Person, "a", token));
|
|
Assert.False(await local.TryReserveUserName(name.ToUpperInvariant(), LocalActorKind.Group, "b", token));
|
|
Assert.True(await local.IsUserNameTaken(name, token));
|
|
Assert.False(await local.TryReserveUserName("admin", LocalActorKind.Person, "c", token));
|
|
Assert.False(await local.TryReserveUserName(LocalActorService.InstanceUserName, LocalActorKind.Person, "c", token));
|
|
}
|
|
}
|
|
}
|