Skip to content

Commit

Permalink
Merge branch 'v8/8.6' into v8/8.7
Browse files Browse the repository at this point in the history
  • Loading branch information
nul800sebastiaan committed Dec 14, 2020
2 parents 7c20ade + 9c4dce5 commit a141467
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 32 deletions.
12 changes: 10 additions & 2 deletions src/Umbraco.Core/Runtime/MainDom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,16 @@ private bool Acquire()

_logger.Info<MainDom>("Acquiring.");

// Get the lock
var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult();
// Get the lock
var acquired = false;
try
{
acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult();
}
catch (Exception ex)
{
_logger.Error<MainDom>(ex, "Error while acquiring");
}

if (!acquired)
{
Expand Down
96 changes: 66 additions & 30 deletions src/Umbraco.Core/Runtime/SqlMainDomLock.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using NPoco;
using System;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
Expand Down Expand Up @@ -49,19 +50,23 @@ public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
}

if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider))
{
throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server");
}

_sqlServerSyntax = sqlServerSyntaxProvider;

_logger.Debug<SqlMainDomLock>("Acquiring lock...");

var tempId = Guid.NewGuid().ToString();

using var db = _dbFactory.CreateDatabase();
using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
IUmbracoDatabase db = null;

try
{
db = _dbFactory.CreateDatabase();
db.BeginTransaction(IsolationLevel.ReadCommitted);

try
{
// wait to get a write lock
Expand Down Expand Up @@ -102,7 +107,8 @@ public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
}
finally
{
transaction.Complete();
db?.CompleteTransaction();
db?.Dispose();
}


Expand Down Expand Up @@ -161,11 +167,11 @@ private void ListeningLoop()
// new MainDom will just take over.
if (_cancellationTokenSource.IsCancellationRequested)
return;

using var db = _dbFactory.CreateDatabase();
using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
IUmbracoDatabase db = null;
try
{
db = _dbFactory.CreateDatabase();
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a read lock
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);

Expand All @@ -189,7 +195,8 @@ private void ListeningLoop()
}
finally
{
transaction.Complete();
db?.CompleteTransaction();
db?.Dispose();
}
}

Expand All @@ -208,34 +215,47 @@ private Task<bool> WaitForExistingAsync(string tempId, int millisecondsTimeout)

return Task.Run(() =>
{
using var db = _dbFactory.CreateDatabase();
var watch = new Stopwatch();
watch.Start();
while (true)
try
{
// poll very often, we need to take over as fast as we can
// local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO
Thread.Sleep(1000);
var acquired = TryAcquire(db, tempId, updatedTempId);
if (acquired.HasValue)
return acquired.Value;
using var db = _dbFactory.CreateDatabase();
if (watch.ElapsedMilliseconds >= millisecondsTimeout)
var watch = new Stopwatch();
watch.Start();
while (true)
{
return AcquireWhenMaxWaitTimeElapsed(db);
// poll very often, we need to take over as fast as we can
// local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO
Thread.Sleep(1000);
var acquired = TryAcquire(db, tempId, updatedTempId);
if (acquired.HasValue)
return acquired.Value;
if (watch.ElapsedMilliseconds >= millisecondsTimeout)
{
return AcquireWhenMaxWaitTimeElapsed(db);
}
}
}
catch (Exception ex)
{
_logger.Error<SqlMainDomLock>(ex, "An error occurred trying to acquire and waiting for existing SqlMainDomLock to shutdown");
return false;
}
}, _cancellationTokenSource.Token);
}

private bool? TryAcquire(IUmbracoDatabase db, string tempId, string updatedTempId)
{
using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
// Creates a separate transaction to the DB instance so we aren't allocating tons of new DB instances for each transaction
// since this is executed in a tight loop

ITransaction transaction = null;

try
{
transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
// get a read lock
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);

Expand Down Expand Up @@ -281,14 +301,18 @@ private Task<bool> WaitForExistingAsync(string tempId, int millisecondsTimeout)
}
finally
{
transaction.Complete();
transaction?.Complete();
transaction?.Dispose();
}

return null; // continue
}

private bool AcquireWhenMaxWaitTimeElapsed(IUmbracoDatabase db)
{
// Creates a separate transaction to the DB instance so we aren't allocating tons of new DB instances for each transaction
// since this is executed in a tight loop

// if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown,
// or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row
// and it's just been left as an orphan row.
Expand All @@ -298,10 +322,12 @@ private bool AcquireWhenMaxWaitTimeElapsed(IUmbracoDatabase db)

_logger.Debug<SqlMainDomLock>("Timeout elapsed, assuming orphan row, acquiring MainDom.");

using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
ITransaction transaction = null;

try
{
transaction = db.GetTransaction(IsolationLevel.ReadCommitted);

_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);

// so now we update the row with our appdomain id
Expand All @@ -324,7 +350,8 @@ private bool AcquireWhenMaxWaitTimeElapsed(IUmbracoDatabase db)
}
finally
{
transaction.Complete();
transaction?.Complete();
transaction?.Dispose();
}
}

Expand Down Expand Up @@ -375,11 +402,12 @@ protected virtual void Dispose(bool disposing)

if (_dbFactory.Configured)
{
using var db = _dbFactory.CreateDatabase();
using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);

IUmbracoDatabase db = null;
try
{
db = _dbFactory.CreateDatabase();
db.BeginTransaction(IsolationLevel.ReadCommitted);

// get a write lock
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);

Expand All @@ -406,7 +434,15 @@ protected virtual void Dispose(bool disposing)
}
finally
{
transaction.Complete();
try
{
db?.CompleteTransaction();
db?.Dispose();
}
catch (Exception ex)
{
_logger.Error<SqlMainDomLock>(ex, "Unexpected error during dispose when completing transaction.");
}
}
}
}
Expand Down

0 comments on commit a141467

Please sign in to comment.