Skip to content

Commit

Permalink
Fix issue with SqlMainDomLock that cannot use implicit lock timeouts … (
Browse files Browse the repository at this point in the history
  • Loading branch information
bergmania committed Mar 12, 2021
1 parent 8cd41ab commit da5351d
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 21 deletions.
40 changes: 24 additions & 16 deletions src/Umbraco.Core/Configuration/GlobalSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GlobalSettings>($"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<GlobalSettings>(
$"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;
}
}
}
15 changes: 10 additions & 5 deletions src/Umbraco.Core/Runtime/SqlMainDomLock.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using NPoco;
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
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;
Expand All @@ -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";
Expand All @@ -40,6 +43,8 @@ public SqlMainDomLock(ILogger logger)
Constants.System.UmbracoConnectionName,
_logger,
new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())));

_lockTimeout = TimeSpan.FromMilliseconds(GlobalSettings.GetSqlWriteLockTimeoutFromConfigFile(logger));
}

public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -284,7 +289,7 @@ private Task<bool> 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<KeyValueDto>("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
Expand All @@ -296,7 +301,7 @@ private Task<bool> 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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit da5351d

Please sign in to comment.