Fix preference loading bugs (#27742)

First bug: if an error occured during pref loading code, it would fail. If the person then readied up, it would likely cause the round to fail to start.

Why could they ready up? The code only checks that the prefs finished loading, not that they finished loading *successfully*. Whoops.

Anyways, now people get kicked if their prefs fail to load. And I improved the error handling.

Second bug: if a user disconnected while their prefs were loading, it would cause an exception. This exception would go unobserved on lobby servers or raise through gameticker on non-lobby servers.

This happened even on a live server once and then triggered the first bug, but idk how.

Fixed this by properly plumbing through cancellation into the preferences loading code. The stuff is now cancelled properly.

Third bug: if somebody has a loadout item with a playtime requirement active, load-time sanitization of player prefs could run into a race condition because the sanitization can happen *before* play time was loaded.

Fixed by moving pref sanitizations to a later stage in the load process.
This commit is contained in:
Pieter-Jan Briers
2024-05-07 06:21:03 +02:00
committed by GitHub
parent 61c1aeddf3
commit 7a38b22ddb
9 changed files with 154 additions and 54 deletions

View File

@@ -439,7 +439,7 @@ namespace Content.Server.Database
public override async Task<((Admin, string? lastUserName)[] admins, AdminRank[])> GetAllAdminAndRanksAsync(
CancellationToken cancel)
{
await using var db = await GetDbImpl();
await using var db = await GetDbImpl(cancel);
var admins = await db.SqliteDbContext.Admin
.Include(a => a.Flags)
@@ -514,23 +514,27 @@ namespace Content.Server.Database
return DateTime.SpecifyKind(time, DateTimeKind.Utc);
}
private async Task<DbGuardImpl> GetDbImpl([CallerMemberName] string? name = null)
private async Task<DbGuardImpl> GetDbImpl(
CancellationToken cancel = default,
[CallerMemberName] string? name = null)
{
LogDbOp(name);
await _dbReadyTask;
if (_msDelay > 0)
await Task.Delay(_msDelay);
await Task.Delay(_msDelay, cancel);
await _prefsSemaphore.WaitAsync();
await _prefsSemaphore.WaitAsync(cancel);
var dbContext = new SqliteServerDbContext(_options());
return new DbGuardImpl(this, dbContext);
}
protected override async Task<DbGuard> GetDb([CallerMemberName] string? name = null)
protected override async Task<DbGuard> GetDb(
CancellationToken cancel = default,
[CallerMemberName] string? name = null)
{
return await GetDbImpl(name).ConfigureAwait(false);
return await GetDbImpl(cancel, name).ConfigureAwait(false);
}
private sealed class DbGuardImpl : DbGuard
@@ -569,9 +573,9 @@ namespace Content.Server.Database
_semaphore = new SemaphoreSlim(maxCount, maxCount);
}
public Task WaitAsync()
public Task WaitAsync(CancellationToken cancel = default)
{
var task = _semaphore.WaitAsync();
var task = _semaphore.WaitAsync(cancel);
if (_synchronous)
{