Hack fix - SQLite deadlock issue for login after session timeout.

This commit is contained in:
Paul Johnson
2022-05-04 10:13:27 +01:00
parent 6752f4b2c6
commit 09b210d5de
2 changed files with 28 additions and 5 deletions

View File

@@ -509,11 +509,14 @@ namespace Umbraco.Cms.Core.Services
public bool ValidateLoginSession(int userId, Guid sessionId)
{
using (ScopeProvider.CreateCoreScope(autoComplete: true))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
return _userRepository.ValidateLoginSession(userId, sessionId);
var result = _userRepository.ValidateLoginSession(userId, sessionId);
scope.Complete();
return result;
}
}
public IDictionary<UserState, int> GetUserStates()
{
using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))

View File

@@ -36,6 +36,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
private readonly IRuntimeState _runtimeState;
private string? _passwordConfigJson;
private bool _passwordConfigInitialized;
private readonly object _sqliteValidateSessionLock = new();
/// <summary>
/// Initializes a new instance of the <see cref="UserRepository" /> class.
@@ -217,6 +218,23 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
}
public bool ValidateLoginSession(int userId, Guid sessionId)
{
// HACK: Avoid a deadlock - BackOfficeCookieOptions OnValidatePrincipal
// After existing session times out and user logs in again ~ 4 requests come in at once that hit the
// "update the validate date" code path, check up the call stack there are a few variables that can make this not occur.
// TODO: more generic fix, do something with ForUpdate? wait on a mutex? add a distributed lock? etc.
if (Database.DatabaseType.IsSqlite())
{
lock (_sqliteValidateSessionLock)
{
return ValidateLoginSessionInternal(userId, sessionId);
}
}
return ValidateLoginSessionInternal(userId, sessionId);
}
private bool ValidateLoginSessionInternal(int userId, Guid sessionId)
{
// with RepeatableRead transaction mode, read-then-update operations can
// cause deadlocks, and the ForUpdate() hint is required to tell the database
@@ -236,15 +254,17 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue)
return false;
//now detect if there's been a timeout
// now detect if there's been a timeout
if (DateTime.UtcNow - found.LastValidatedUtc > _globalSettings.TimeOut)
{
//timeout detected, update the record
// timeout detected, update the record
Logger.LogDebug("ClearLoginSession for sessionId {sessionId}", sessionId);
ClearLoginSession(sessionId);
return false;
}
//update the validate date
// update the validate date
Logger.LogDebug("Updating LastValidatedUtc for sessionId {sessionId}", sessionId);
found.LastValidatedUtc = DateTime.UtcNow;
Database.Update(found);
return true;