From da5351dfcf23daad69fcd73eb74811456ffc34c0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 12 Mar 2021 09:23:32 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20issue=20with=20SqlMainDomLock=20that=20ca?= =?UTF-8?q?nnot=20use=20implicit=20lock=20timeouts=20=E2=80=A6=20(#9973)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configuration/GlobalSettings.cs | 40 +++++++++++-------- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 15 ++++--- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index b7dce212855a..ba5baf9e871b 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -409,26 +409,34 @@ public int SqlWriteLockTimeOut { if (_sqlWriteLockTimeOut != default) return _sqlWriteLockTimeOut; - var timeOut = 5000; // 5 seconds - var appSettingSqlWriteLockTimeOut = ConfigurationManager.AppSettings[Constants.AppSettings.SqlWriteLockTimeOut]; - if(int.TryParse(appSettingSqlWriteLockTimeOut, out var configuredTimeOut)) - { - // Only apply this setting if it's not excessively high or low - const int minimumTimeOut = 100; - const int maximumTimeOut = 20000; - if (configuredTimeOut >= minimumTimeOut && configuredTimeOut <= maximumTimeOut) // between 0.1 and 20 seconds - { - timeOut = configuredTimeOut; - } - else - { - Current.Logger.Warn($"The `{Constants.AppSettings.SqlWriteLockTimeOut}` setting in web.config is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms, defaulting back to {timeOut}"); - } - } + var timeOut = GetSqlWriteLockTimeoutFromConfigFile(Current.Logger); _sqlWriteLockTimeOut = timeOut; return _sqlWriteLockTimeOut; } } + + internal static int GetSqlWriteLockTimeoutFromConfigFile(ILogger logger) + { + var timeOut = 5000; // 5 seconds + var appSettingSqlWriteLockTimeOut = ConfigurationManager.AppSettings[Constants.AppSettings.SqlWriteLockTimeOut]; + if (int.TryParse(appSettingSqlWriteLockTimeOut, out var configuredTimeOut)) + { + // Only apply this setting if it's not excessively high or low + const int minimumTimeOut = 100; + const int maximumTimeOut = 20000; + if (configuredTimeOut >= minimumTimeOut && configuredTimeOut <= maximumTimeOut) // between 0.1 and 20 seconds + { + timeOut = configuredTimeOut; + } + else + { + logger.Warn( + $"The `{Constants.AppSettings.SqlWriteLockTimeOut}` setting in web.config is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms, defaulting back to {timeOut}"); + } + } + + return timeOut; + } } } diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index f58b279a8d71..6a82486c436b 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -1,5 +1,6 @@ using NPoco; using System; +using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Diagnostics; @@ -7,6 +8,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -18,6 +20,7 @@ namespace Umbraco.Core.Runtime { internal class SqlMainDomLock : IMainDomLock { + private readonly TimeSpan _lockTimeout; private string _lockId; private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom"; private const string UpdatedSuffix = "_updated"; @@ -40,6 +43,8 @@ public SqlMainDomLock(ILogger logger) Constants.System.UmbracoConnectionName, _logger, new Lazy(() => new MapperCollection(Enumerable.Empty()))); + + _lockTimeout = TimeSpan.FromMilliseconds(GlobalSettings.GetSqlWriteLockTimeoutFromConfigFile(logger)); } public async Task AcquireLockAsync(int millisecondsTimeout) @@ -198,7 +203,7 @@ private void ListeningLoop() db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.ReadLock(db, _lockTimeout, Constants.Locks.MainDom); if (!IsMainDomValue(_lockId, db)) { @@ -284,7 +289,7 @@ private Task WaitForExistingAsync(string tempId, int millisecondsTimeout) { transaction = db.GetTransaction(IsolationLevel.ReadCommitted); // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.ReadLock(db, _lockTimeout, Constants.Locks.MainDom); // the row var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); @@ -296,7 +301,7 @@ private Task WaitForExistingAsync(string tempId, int millisecondsTimeout) // which indicates that we // can acquire it and it has shutdown. - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, _lockTimeout, Constants.Locks.MainDom); // so now we update the row with our appdomain id InsertLockRecord(_lockId, db); @@ -355,7 +360,7 @@ private bool AcquireWhenMaxWaitTimeElapsed(IUmbracoDatabase db) { transaction = db.GetTransaction(IsolationLevel.ReadCommitted); - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, _lockTimeout, Constants.Locks.MainDom); // so now we update the row with our appdomain id InsertLockRecord(_lockId, db); @@ -438,7 +443,7 @@ protected virtual void Dispose(bool disposing) db.BeginTransaction(IsolationLevel.ReadCommitted); // get a write lock - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, _lockTimeout, Constants.Locks.MainDom); // When we are disposed, it means we have released the MainDom lock // and called all MainDom release callbacks, in this case