diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index c413557a27..f1f6ee3afc 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -157,7 +157,15 @@ namespace Umbraco.Core.Runtime _logger.LogInformation("Acquiring."); // Get the lock - var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); + var acquired = false; + try + { + acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while acquiring"); + } if (!acquired) { diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index 41bdaa9e01..c73a817327 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -7,7 +7,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Umbraco.Core.Configuration; +using NPoco; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Persistence; @@ -42,7 +42,7 @@ namespace Umbraco.Core.Runtime loggerFactory, globalSettings, connectionStrings, - new Lazy(() => new MapperCollection(Enumerable.Empty())), + new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty())), dbProviderFactoryCreator); MainDomKey = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash(); @@ -57,7 +57,9 @@ namespace Umbraco.Core.Runtime } if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider)) + { throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server"); + } _sqlServerSyntax = sqlServerSyntaxProvider; @@ -65,11 +67,13 @@ namespace Umbraco.Core.Runtime 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 @@ -110,7 +114,8 @@ namespace Umbraco.Core.Runtime } finally { - transaction.Complete(); + db?.CompleteTransaction(); + db?.Dispose(); } @@ -172,11 +177,11 @@ namespace Umbraco.Core.Runtime 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); @@ -202,7 +207,8 @@ namespace Umbraco.Core.Runtime } finally { - transaction.Complete(); + db?.CompleteTransaction(); + db?.Dispose(); } } @@ -221,34 +227,47 @@ namespace Umbraco.Core.Runtime 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); + using var db = _dbFactory.CreateDatabase(); - var acquired = TryAcquire(db, tempId, updatedTempId); - if (acquired.HasValue) - return acquired.Value; - - 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.LogError(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); @@ -294,7 +313,8 @@ namespace Umbraco.Core.Runtime } finally { - transaction.Complete(); + transaction?.Complete(); + transaction?.Dispose(); } return null; // continue @@ -302,6 +322,9 @@ namespace Umbraco.Core.Runtime 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. @@ -311,10 +334,12 @@ namespace Umbraco.Core.Runtime _logger.LogDebug("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 @@ -337,7 +362,8 @@ namespace Umbraco.Core.Runtime } finally { - transaction.Complete(); + transaction?.Complete(); + transaction?.Dispose(); } } @@ -390,11 +416,12 @@ namespace Umbraco.Core.Runtime 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); @@ -421,7 +448,15 @@ namespace Umbraco.Core.Runtime } finally { - transaction.Complete(); + try + { + db?.CompleteTransaction(); + db?.Dispose(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error during dispose when completing transaction."); + } } } } diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index d87ca43452..44ff1d1cdf 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -239,9 +239,10 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi // Ask to re-enter preview mode? const localizeVarsFallback = { - "returnToPreviewHeadline": "Preview content?", - "returnToPreviewDescription":"You have ended preview mode, do you want to continue previewing this content?", - "returnToPreviewButton":"Preview" + "returnToPreviewHeadline": "Preview website?", + "returnToPreviewDescription":"You have ended preview mode, do you want to enable it again to view the latest saved version of your website?", + "returnToPreviewAcceptButton":"Preview latest version", + "returnToPreviewDeclineButton":"View published version" }; const umbLocalizedVars = Object.assign(localizeVarsFallback, $window.umbLocalizedVars); @@ -344,10 +345,20 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
${umbLocalizedVars.returnToPreviewDescription}
`; con.appendChild(modal); + var declineButton = document.createElement("button"); + declineButton.type = "button"; + declineButton.innerHTML = umbLocalizedVars.returnToPreviewDeclineButton; + declineButton.addEventListener("click", () => { + bodyEl.removeChild(fragment); + $scope.exitPreview(); + hasPreviewDialog = false; + }); + modal.appendChild(declineButton); + var continueButton = document.createElement("button"); continueButton.type = "button"; continueButton.className = "umbraco-preview-dialog__continue"; - continueButton.innerHTML = umbLocalizedVars.returnToPreviewButton; + continueButton.innerHTML = umbLocalizedVars.returnToPreviewAcceptButton; continueButton.addEventListener("click", () => { bodyEl.removeChild(fragment); reenterPreviewMode(); diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Preview.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Preview.cshtml index bc72c1649b..382e0acce2 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Preview.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Preview.cshtml @@ -28,7 +28,8 @@ var OpenWebsiteTitle = LocalizedTextService.Localize("preview", "openWebsiteTitle"); var returnToPreviewHeadline = LocalizedTextService.Localize("preview", "returnToPreviewHeadline"); var returnToPreviewDescription = LocalizedTextService.Localize("preview", "returnToPreviewDescription"); - var returnToPreviewButton = LocalizedTextService.Localize("preview", "returnToPreviewButton"); + var returnToPreviewAcceptButton = LocalizedTextService.Localize("preview", "returnToPreviewAcceptButton"); + var returnToPreviewDeclineButton = LocalizedTextService.Localize("preview", "returnToPreviewDeclineButton"); } @@ -44,7 +45,8 @@ window.umbLocalizedVars = { 'returnToPreviewHeadline': '@returnToPreviewHeadline', 'returnToPreviewDescription':'@returnToPreviewDescription', - 'returnToPreviewButton':'@returnToPreviewButton' + 'returnToPreviewAcceptButton':'@returnToPreviewAcceptButton', + 'returnToPreviewDeclineButton':'@returnToPreviewDeclineButton' }; diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml index 857129b1c1..b6d10589fe 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml @@ -1439,7 +1439,7 @@ Mange hilsner fra Umbraco robotten Dette benyttes ikke for en Element-type Du har lavet ændringer til denne egenskab. Er du sikker på at du vil kassere dem? Visning - Flyt label over editoren + Label hen over (fuld brede) Tilføj sprog @@ -1873,8 +1873,9 @@ Mange hilsner fra Umbraco robotten Vis i nyt vindue Åben forhåndsvisning i nyt vindue Forhåndsvisning af indholdet? - Du har afslutet forhåndsvisning, vil du se dette indhold i forhåndsvisning? - Fortsæt + Du har afslutet forhåndsvisning, vil du starte forhåndsvisning igen for at se seneste gemte version af indholdet? + Start forhåndsvisning + Se udgivet indhold Se udgivet indhold? Du er i forhåndsvisning, vil du afslutte for at se den udgivet version? Se udgivet version diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml index 6916b00e1c..1a1bf8f840 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml @@ -1699,7 +1699,7 @@ To manage your website, simply open the Umbraco back office and start adding con This is not applicable for an Element type You have made changes to this property. Are you sure you want to discard them? Appearance - Display label on top of editor. + Label above (full-width) Add language @@ -2536,9 +2536,10 @@ To manage your website, simply open the Umbraco back office and start adding con End preview mode Preview website Open website in preview mode - Preview content? - You have ended preview mode, do you want to continue previewing this content? - Preview + Preview website? + ou have ended preview mode, do you want to enable it again to view the latest saved version of your website? + Preview latest version + View published version View published version? You are in Preview Mode, do you want exit in order to view the published version of your website? View published version diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml index cacb8288b3..0bbba93c89 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml @@ -1717,7 +1717,7 @@ To manage your website, simply open the Umbraco back office and start adding con This is not applicable for an element type You have made changes to this property. Are you sure you want to discard them? Appearance - Display label on top of editor. + Label above (full-width) Add language @@ -2556,9 +2556,10 @@ To manage your website, simply open the Umbraco back office and start adding con End preview mode Preview website Open website in preview mode - Preview content? - You have ended preview mode, do you want to continue previewing this content? - Preview + Preview website? + ou have ended preview mode, do you want to enable it again to view the latest saved version of your website? + Preview latest version + View published version View published version? You are in Preview Mode, do you want exit in order to view the published version of your website? View published version