mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 15:08:47 -07:00
fix: repair a missing entry sub-room under the bootstrap lock
From the local CodeRabbit review: - Repair rooms that lack an OrderIndex 0 (entry) sub-room even when they have other sub-rooms, not only rooms with none. - Every service runs the database bootstrap at startup, so on Postgres replicas booting together could hand out the same scene ids (max + 1) and one would fail startup on the primary key. Run the repair in a transaction holding the bootstrap's advisory lock.
This commit is contained in:
@@ -118,6 +118,24 @@ public sealed class SubRoomsAndInstances2023Tests : IClassFixture<DorkNetServerF
|
||||
Assert.Equal((0, 0), await service.RepairRoomScenesAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Startup_repair_restores_a_missing_entry_sub_room_next_to_others()
|
||||
{
|
||||
var roomId = await SeedRoomAsync(1, accessibility: 0, scenes: new[] { "Home", "Back" });
|
||||
await using var scope = _factory.Services.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<DorkNetDbContext>();
|
||||
await db.RoomScenes.Where(s => s.RoomId == roomId && s.OrderIndex == 0).ExecuteDeleteAsync();
|
||||
|
||||
var service = scope.ServiceProvider.GetRequiredService<RoomService>();
|
||||
var (added, _) = await service.RepairRoomScenesAsync();
|
||||
Assert.True(added >= 1);
|
||||
|
||||
var scenes = await db.RoomScenes.AsNoTracking().Where(s => s.RoomId == roomId)
|
||||
.OrderBy(s => s.OrderIndex).Select(s => new { s.OrderIndex, s.Name }).ToListAsync();
|
||||
Assert.Equal(new[] { (0, "Home"), (1, "Back") }, scenes.Select(s => (s.OrderIndex, s.Name)));
|
||||
Assert.Equal((0, 0), await service.RepairRoomScenesAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Instance_browser_lists_the_private_instance_with_its_players_for_allowed_callers()
|
||||
{
|
||||
|
||||
@@ -585,15 +585,24 @@ public class RoomService(DorkNetDbContext db)
|
||||
///
|
||||
/// <para>Scene ids are assigned explicitly (max + 1) for the same reason
|
||||
/// the clone handler does: seeded rows were inserted with manual ids
|
||||
/// without advancing the Postgres identity sequence.</para>
|
||||
/// Returns (rooms given a scene, scenes renamed).</summary>
|
||||
/// without advancing the Postgres identity sequence. Every service that
|
||||
/// runs the database bootstrap calls this at startup, so on Postgres the
|
||||
/// whole repair holds the bootstrap's advisory lock: replicas booting
|
||||
/// together take turns instead of handing out the same ids.</para>
|
||||
/// Returns (rooms given an entry scene, scenes renamed).</summary>
|
||||
public async Task<(int ScenesAdded, int ScenesRenamed)> RepairRoomScenesAsync()
|
||||
{
|
||||
var roomsWithoutScenes = await db.Rooms
|
||||
.Where(r => !r.IsDormRoom && !db.RoomScenes.Any(s => s.RoomId == r.Id))
|
||||
await using var tx = await db.Database.BeginTransactionAsync();
|
||||
if (db.Database.IsNpgsql())
|
||||
await db.Database.ExecuteSqlRawAsync("SELECT pg_advisory_xact_lock(1146246987);");
|
||||
|
||||
// The entry sub-room is OrderIndex 0 (its blob is the room's own front
|
||||
// door), so a room missing it is repaired even when it has others.
|
||||
var roomsWithoutEntry = await db.Rooms
|
||||
.Where(r => !r.IsDormRoom && !db.RoomScenes.Any(s => s.RoomId == r.Id && s.OrderIndex == 0))
|
||||
.ToListAsync();
|
||||
var nextSceneId = await db.RoomScenes.MaxAsync(s => (long?)s.Id) ?? 0L;
|
||||
foreach (var room in roomsWithoutScenes)
|
||||
foreach (var room in roomsWithoutEntry)
|
||||
{
|
||||
var scene = NewHomeScene(room);
|
||||
scene.Id = ++nextSceneId;
|
||||
@@ -606,9 +615,10 @@ public class RoomService(DorkNetDbContext db)
|
||||
foreach (var scene in unnamed)
|
||||
scene.Name = DefaultSubRoomName(scene.OrderIndex);
|
||||
|
||||
if (roomsWithoutScenes.Count > 0 || unnamed.Count > 0)
|
||||
if (roomsWithoutEntry.Count > 0 || unnamed.Count > 0)
|
||||
await db.SaveChangesAsync();
|
||||
return (roomsWithoutScenes.Count, unnamed.Count);
|
||||
await tx.CommitAsync();
|
||||
return (roomsWithoutEntry.Count, unnamed.Count);
|
||||
}
|
||||
|
||||
/// <summary>Run once at startup to backfill <see cref="RoomEntity"/>
|
||||
|
||||
Reference in New Issue
Block a user