diff --git a/src/Prima/Services/DbService.cs b/src/Prima/Services/DbService.cs index 3b52edc..f109c5f 100644 --- a/src/Prima/Services/DbService.cs +++ b/src/Prima/Services/DbService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -13,6 +14,7 @@ public class DbService : IDbService { private const string ConnectionString = "mongodb://localhost:27017"; private const string DbName = "PrimaDb"; + private const double LockTimeoutSeconds = 30; // Hide types of the database implementation from callers. public GlobalConfiguration Config @@ -48,9 +50,12 @@ public GlobalConfiguration Config private readonly ILogger _logger; + private readonly SemaphoreSlim _lock; + public DbService(ILogger logger) { _logger = logger; + _lock = new SemaphoreSlim(80, 80); var client = new MongoClient(ConnectionString); var database = client.GetDatabase(DbName); @@ -96,11 +101,14 @@ public DbService(ILogger logger) _ephemeralPins.EstimatedDocumentCount()); } - public async Task GetUserByCharacterInfo(string? world, string? characterName) + public Task GetUserByCharacterInfo(string? world, string? characterName) { - if (characterName == null || world == null) return null; - _logger.LogInformation("Fetching user: ({World}) {CharacterName}", world, characterName); - return await _users.Find(u => u.World == world && u.Name == characterName).FirstOrDefaultAsync(); + return WithLock(async () => + { + if (characterName == null || world == null) return null; + _logger.LogInformation("Fetching user: ({World}) {CharacterName}", world, characterName); + return await _users.Find(u => u.World == world && u.Name == characterName).FirstOrDefaultAsync(); + }); } public async Task SetGlobalConfigurationProperty(string key, string value) @@ -426,5 +434,23 @@ public async Task DeleteChannelDescription(ulong channelId) await _channelDescriptions.DeleteOneAsync(cd => cd.ChannelId == channelId); } } + + private async Task WithLock(Func> action) + { + if (!await _lock.WaitAsync(TimeSpan.FromSeconds(LockTimeoutSeconds)).ConfigureAwait(false)) + { + _logger.LogWarning("Could not acquire database lock"); + throw new TimeoutException("Could not acquire database lock"); + } + + try + { + return await action(); + } + finally + { + _lock.Release(); + } + } } } \ No newline at end of file