mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -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());
|
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]
|
[Fact]
|
||||||
public async Task Instance_browser_lists_the_private_instance_with_its_players_for_allowed_callers()
|
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
|
/// <para>Scene ids are assigned explicitly (max + 1) for the same reason
|
||||||
/// the clone handler does: seeded rows were inserted with manual ids
|
/// the clone handler does: seeded rows were inserted with manual ids
|
||||||
/// without advancing the Postgres identity sequence.</para>
|
/// without advancing the Postgres identity sequence. Every service that
|
||||||
/// Returns (rooms given a scene, scenes renamed).</summary>
|
/// 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()
|
public async Task<(int ScenesAdded, int ScenesRenamed)> RepairRoomScenesAsync()
|
||||||
{
|
{
|
||||||
var roomsWithoutScenes = await db.Rooms
|
await using var tx = await db.Database.BeginTransactionAsync();
|
||||||
.Where(r => !r.IsDormRoom && !db.RoomScenes.Any(s => s.RoomId == r.Id))
|
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();
|
.ToListAsync();
|
||||||
var nextSceneId = await db.RoomScenes.MaxAsync(s => (long?)s.Id) ?? 0L;
|
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);
|
var scene = NewHomeScene(room);
|
||||||
scene.Id = ++nextSceneId;
|
scene.Id = ++nextSceneId;
|
||||||
@@ -606,9 +615,10 @@ public class RoomService(DorkNetDbContext db)
|
|||||||
foreach (var scene in unnamed)
|
foreach (var scene in unnamed)
|
||||||
scene.Name = DefaultSubRoomName(scene.OrderIndex);
|
scene.Name = DefaultSubRoomName(scene.OrderIndex);
|
||||||
|
|
||||||
if (roomsWithoutScenes.Count > 0 || unnamed.Count > 0)
|
if (roomsWithoutEntry.Count > 0 || unnamed.Count > 0)
|
||||||
await db.SaveChangesAsync();
|
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"/>
|
/// <summary>Run once at startup to backfill <see cref="RoomEntity"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user