{{syntax.content.0.value}}
From de4b3af28f091c4dec61df5eb17203eb98860528 Mon Sep 17 00:00:00 2001
From: Paul Johnson
Date: Thu, 24 Feb 2022 14:38:33 +0000
Subject: [PATCH 07/22] Resolve various points related to deficiencies in
FileSystemMainDomLock (#12052)
* Resolve various points related to deficiencies in FileSystemMainDomLock
See GH #12049
* Increasing backoff time for retry when deleting lock release signal file
However reducing max tries, really hoping this never actually happens
and if it does, failing to boot ASAP seems reasonable.
---
.../Runtime/FileSystemMainDomLock.cs | 48 ++++++++++++-------
1 file changed, 32 insertions(+), 16 deletions(-)
diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
index d18e4085a0..f77cb74c3f 100644
--- a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@@ -11,21 +12,22 @@ namespace Umbraco.Cms.Infrastructure.Runtime
{
internal class FileSystemMainDomLock : IMainDomLock
{
- private readonly ILogger _log;
-
+ private readonly ILogger _logger;
private readonly CancellationTokenSource _cancellationTokenSource = new();
-
private readonly string _lockFilePath;
private readonly string _releaseSignalFilePath;
private FileStream _lockFileStream;
+ private Task _listenForReleaseSignalFileTask;
+
+ private const int s_maxTriesRemovingLockReleaseSignalFile = 3;
public FileSystemMainDomLock(
- ILogger log,
+ ILogger logger,
IMainDomKeyGenerator mainDomKeyGenerator,
IHostingEnvironment hostingEnvironment)
{
- _log = log;
+ _logger = logger;
var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock";
_lockFilePath = Path.Combine(hostingEnvironment.LocalTempPath, lockFileName);
@@ -41,20 +43,20 @@ namespace Umbraco.Cms.Infrastructure.Runtime
{
try
{
- _log.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
+ _logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
_lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
DeleteLockReleaseFile();
return Task.FromResult(true);
}
catch (IOException)
{
- _log.LogDebug("Couldn't obtain MainDom lock file handle, signalling for release of {lockFilePath}", _lockFilePath);
+ _logger.LogDebug("Couldn't obtain MainDom lock file handle, signalling for release of {lockFilePath}", _lockFilePath);
CreateLockReleaseFile();
Thread.Sleep(500);
}
catch (Exception ex)
{
- _log.LogError(ex, "Unexpected exception attempting to obtain MainDom lock file handle {lockFilePath}, giving up", _lockFilePath);
+ _logger.LogError(ex, "Unexpected exception attempting to obtain MainDom lock file handle {lockFilePath}, giving up", _lockFilePath);
return Task.FromResult(false);
}
}
@@ -64,13 +66,22 @@ namespace Umbraco.Cms.Infrastructure.Runtime
}
// Create a long running task to poll to check if anyone has created a lock release file.
- public Task ListenAsync() =>
- Task.Factory.StartNew(
+ public Task ListenAsync()
+ {
+ if (_listenForReleaseSignalFileTask != null)
+ {
+ return _listenForReleaseSignalFileTask;
+ }
+
+ _listenForReleaseSignalFileTask = Task.Factory.StartNew(
ListeningLoop,
_cancellationTokenSource.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
+ return _listenForReleaseSignalFileTask;
+ }
+
public void Dispose()
{
_lockFileStream?.Close();
@@ -86,24 +97,29 @@ namespace Umbraco.Cms.Infrastructure.Runtime
}
catch (Exception ex)
{
- _log.LogError(ex, "Unexpected exception attempting to create lock release signal file {file}", _releaseSignalFilePath);
+ _logger.LogError(ex, "Unexpected exception attempting to create lock release signal file {file}", _releaseSignalFilePath);
}
}
private void DeleteLockReleaseFile()
{
- while (File.Exists(_releaseSignalFilePath))
+ List encounteredExceptions = new();
+ for (var i = 0; i < s_maxTriesRemovingLockReleaseSignalFile; i++)
{
try
{
File.Delete(_releaseSignalFilePath);
+ return;
}
catch (Exception ex)
{
- _log.LogError(ex, "Unexpected exception attempting to delete release signal file {file}", _releaseSignalFilePath);
- Thread.Sleep(500);
+ _logger.LogError(ex, "Unexpected exception attempting to delete release signal file {file}", _releaseSignalFilePath);
+ encounteredExceptions.Add(ex);
+ Thread.Sleep(500 * (i + 1));
}
}
+
+ throw new ApplicationException($"Failed to remove lock release signal file {_releaseSignalFilePath}", new AggregateException(encounteredExceptions));
}
private void ListeningLoop()
@@ -112,13 +128,13 @@ namespace Umbraco.Cms.Infrastructure.Runtime
{
if (_cancellationTokenSource.IsCancellationRequested)
{
- _log.LogDebug("ListenAsync Task canceled, exiting loop");
+ _logger.LogDebug("ListenAsync Task canceled, exiting loop");
return;
}
if (File.Exists(_releaseSignalFilePath))
{
- _log.LogDebug("Found lock release signal file, releasing lock on {lockFilePath}", _lockFilePath);
+ _logger.LogDebug("Found lock release signal file, releasing lock on {lockFilePath}", _lockFilePath);
_lockFileStream?.Close();
_lockFileStream = null;
break;
From 4351ce6ee4b544bb4575d15f9b70c61ffacb6b55 Mon Sep 17 00:00:00 2001
From: Paul Johnson
Date: Fri, 25 Feb 2022 08:22:37 +0000
Subject: [PATCH 08/22] Further changes requested during review of #12049
(#12053)
---
.../Configuration/Models/GlobalSettings.cs | 13 ++++
.../UmbracoBuilder.CoreServices.cs | 2 +-
.../Runtime/FileSystemMainDomLock.cs | 70 ++++++-------------
.../Runtime/SqlMainDomLock.cs | 2 +-
.../Runtime/FileSystemMainDomLockTests.cs | 13 ++--
5 files changed, 45 insertions(+), 55 deletions(-)
diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index 53584af1d1..31068efd9f 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -29,6 +29,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml";
internal const string StaticSqlWriteLockTimeOut = "00:00:05";
internal const bool StaticSanitizeTinyMce = false;
+ internal const int StaticMainDomReleaseSignalPollingInterval = 2000;
///
/// Gets or sets a value for the reserved URLs (must end with a comma).
@@ -145,6 +146,18 @@ namespace Umbraco.Cms.Core.Configuration.Models
///
public string MainDomKeyDiscriminator { get; set; } = string.Empty;
+ ///
+ /// Gets or sets the duration (in milliseconds) for which the MainDomLock release signal polling task should sleep.
+ ///
+ ///
+ /// Doesn't apply to MainDomSemaphoreLock.
+ ///
+ /// The default value is 2000ms.
+ ///
+ ///
+ [DefaultValue(StaticMainDomReleaseSignalPollingInterval)]
+ public int MainDomReleaseSignalPollingInterval { get; set; } = StaticMainDomReleaseSignalPollingInterval;
+
///
/// Gets or sets the telemetry ID.
///
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 1c8b3ec0de..c4b9a6367c 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -234,7 +234,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
if (globalSettings.Value.MainDomLock == "FileSystemMainDomLock")
{
- return new FileSystemMainDomLock(loggerFactory.CreateLogger(), mainDomKeyGenerator, hostingEnvironment);
+ return new FileSystemMainDomLock(loggerFactory.CreateLogger(), mainDomKeyGenerator, hostingEnvironment, factory.GetRequiredService>());
}
return globalSettings.Value.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
index f77cb74c3f..32b93513b4 100644
--- a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
@@ -1,10 +1,11 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Runtime;
@@ -13,6 +14,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
internal class FileSystemMainDomLock : IMainDomLock
{
private readonly ILogger _logger;
+ private readonly IOptionsMonitor _globalSettings;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly string _lockFilePath;
private readonly string _releaseSignalFilePath;
@@ -20,14 +22,14 @@ namespace Umbraco.Cms.Infrastructure.Runtime
private FileStream _lockFileStream;
private Task _listenForReleaseSignalFileTask;
- private const int s_maxTriesRemovingLockReleaseSignalFile = 3;
-
public FileSystemMainDomLock(
ILogger logger,
IMainDomKeyGenerator mainDomKeyGenerator,
- IHostingEnvironment hostingEnvironment)
+ IHostingEnvironment hostingEnvironment,
+ IOptionsMonitor globalSettings)
{
_logger = logger;
+ _globalSettings = globalSettings;
var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock";
_lockFilePath = Path.Combine(hostingEnvironment.LocalTempPath, lockFileName);
@@ -45,18 +47,18 @@ namespace Umbraco.Cms.Infrastructure.Runtime
{
_logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
_lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
- DeleteLockReleaseFile();
+ DeleteLockReleaseSignalFile();
return Task.FromResult(true);
}
catch (IOException)
{
_logger.LogDebug("Couldn't obtain MainDom lock file handle, signalling for release of {lockFilePath}", _lockFilePath);
- CreateLockReleaseFile();
- Thread.Sleep(500);
+ CreateLockReleaseSignalFile();
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected exception attempting to obtain MainDom lock file handle {lockFilePath}, giving up", _lockFilePath);
+ _lockFileStream?.Close();
return Task.FromResult(false);
}
}
@@ -65,6 +67,12 @@ namespace Umbraco.Cms.Infrastructure.Runtime
return Task.FromResult(false);
}
+ public void CreateLockReleaseSignalFile() =>
+ _ = File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
+
+ public void DeleteLockReleaseSignalFile() =>
+ File.Delete(_releaseSignalFilePath);
+
// Create a long running task to poll to check if anyone has created a lock release file.
public Task ListenAsync()
{
@@ -82,46 +90,6 @@ namespace Umbraco.Cms.Infrastructure.Runtime
return _listenForReleaseSignalFileTask;
}
- public void Dispose()
- {
- _lockFileStream?.Close();
- _lockFileStream = null;
- }
-
- private void CreateLockReleaseFile()
- {
- try
- {
- // Dispose immediately to release the file handle so it's easier to cleanup in any process.
- using FileStream releaseFileStream = File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Unexpected exception attempting to create lock release signal file {file}", _releaseSignalFilePath);
- }
- }
-
- private void DeleteLockReleaseFile()
- {
- List encounteredExceptions = new();
- for (var i = 0; i < s_maxTriesRemovingLockReleaseSignalFile; i++)
- {
- try
- {
- File.Delete(_releaseSignalFilePath);
- return;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Unexpected exception attempting to delete release signal file {file}", _releaseSignalFilePath);
- encounteredExceptions.Add(ex);
- Thread.Sleep(500 * (i + 1));
- }
- }
-
- throw new ApplicationException($"Failed to remove lock release signal file {_releaseSignalFilePath}", new AggregateException(encounteredExceptions));
- }
-
private void ListeningLoop()
{
while (true)
@@ -140,8 +108,14 @@ namespace Umbraco.Cms.Infrastructure.Runtime
break;
}
- Thread.Sleep(2000);
+ Thread.Sleep(_globalSettings.CurrentValue.MainDomReleaseSignalPollingInterval);
}
}
+
+ public void Dispose()
+ {
+ _lockFileStream?.Close();
+ _lockFileStream = null;
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
index 635b0b9c28..8a6698b92a 100644
--- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
@@ -236,7 +236,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
{
// poll every couple of seconds
// local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO
- Thread.Sleep(2000);
+ Thread.Sleep(_globalSettings.Value.MainDomReleaseSignalPollingInterval);
if (!_dbFactory.Configured)
{
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs
index 59442a683a..c709a756b8 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs
@@ -1,7 +1,10 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Moq;
using NUnit.Framework;
+using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Infrastructure.Runtime;
@@ -31,8 +34,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Runtime
LockFilePath = Path.Combine(HostingEnvironment.LocalTempPath, lockFileName);
LockReleaseFilePath = LockFilePath + "_release";
+ var globalSettings = Mock.Of>();
+ Mock.Get(globalSettings).Setup(x => x.CurrentValue).Returns(new GlobalSettings());
+
var log = GetRequiredService>();
- FileSystemMainDomLock = new FileSystemMainDomLock(log, MainDomKeyGenerator, HostingEnvironment);
+ FileSystemMainDomLock = new FileSystemMainDomLock(log, MainDomKeyGenerator, HostingEnvironment, globalSettings);
}
[TearDown]
@@ -79,10 +85,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Runtime
var before = await sut.AcquireLockAsync(1000);
- await using (_ = File.Open(LockReleaseFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
- {
- }
-
+ sut.CreateLockReleaseSignalFile();
await sut.ListenAsync();
var after = await sut.AcquireLockAsync(1000);
From 4a6c409a1f6b222646daa4a6796ce11d6a812fb5 Mon Sep 17 00:00:00 2001
From: Paul Johnson
Date: Fri, 25 Feb 2022 10:56:45 +0000
Subject: [PATCH 09/22] Explicitly close release signal file. (#12057)
---
.../Runtime/FileSystemMainDomLock.cs | 3 ++-
.../Runtime/FileSystemMainDomLockTests.cs | 23 ++++++++++++++-----
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
index 32b93513b4..c4cbcef588 100644
--- a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
@@ -68,7 +68,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
}
public void CreateLockReleaseSignalFile() =>
- _ = File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
+ File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete)
+ .Close();
public void DeleteLockReleaseSignalFile() =>
File.Delete(_releaseSignalFilePath);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs
index c709a756b8..ebb9b8a6a7 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -44,13 +45,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Runtime
[TearDown]
public void TearDown()
{
- while (File.Exists(LockFilePath))
+ CleanupTestFile(LockFilePath);
+ CleanupTestFile(LockReleaseFilePath);
+ }
+
+ private static void CleanupTestFile(string path)
+ {
+ for (var i = 0; i < 3; i++)
{
- File.Delete(LockFilePath);
- }
- while (File.Exists(LockReleaseFilePath))
- {
- File.Delete(LockReleaseFilePath);
+ try
+ {
+ File.Delete(path);
+ return;
+ }
+ catch
+ {
+ Thread.Sleep(500 * (i + 1));
+ }
}
}
From 0c7ef060314440ef3d59e5e7f7bb1f5fd3b62be2 Mon Sep 17 00:00:00 2001
From: Mole
Date: Mon, 28 Feb 2022 13:59:39 +0100
Subject: [PATCH 10/22] V9: Fix missing site identifier (#12040)
* Add SiteIdentifierService
* Use SiteIdentifierService in TelemetryService
* Use SiteIdentifierService when installing
* Remove timeout
* Use TryGetOrCreateSiteIdentifier in TelemetryService
* Add site identifier to dashboard url
* Fix and add tests
* Don't accept empty guid as valid site identifier
* Fix dashboard controller
* Fix site id query parameter
* Use Optionsmonitor onchange
Co-authored-by: nikolajlauridsen
Co-authored-by: Bjarke Berg
---
.../DependencyInjection/UmbracoBuilder.cs | 1 +
.../InstallSteps/TelemetryIdentifierStep.cs | 35 ++++----
.../Telemetry/ISiteIdentifierService.cs | 31 +++++++
.../Telemetry/SiteIdentifierService.cs | 81 +++++++++++++++++++
.../Telemetry/TelemetryService.cs | 32 ++------
.../UmbracoBuilder.Installer.cs | 10 ++-
.../HostedServices/ReportSiteTask.cs | 3 -
.../Controllers/DashboardController.cs | 38 ++++++++-
.../Telemetry/SiteIdentifierServiceTests.cs | 77 ++++++++++++++++++
.../Telemetry/TelemetryServiceTests.cs | 59 +++++++-------
10 files changed, 287 insertions(+), 80 deletions(-)
create mode 100644 src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs
create mode 100644 src/Umbraco.Core/Telemetry/SiteIdentifierService.cs
create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SiteIdentifierServiceTests.cs
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index c4a95d45e5..235dc71252 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -262,6 +262,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton();
// Register telemetry service used to gather data about installed packages
+ Services.AddUnique();
Services.AddUnique();
// Register a noop IHtmlSanitizer to be replaced
diff --git a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
index 37769afc53..d95fa6919d 100644
--- a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
+++ b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs
@@ -1,10 +1,13 @@
using System;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Install.Models;
+using Umbraco.Cms.Core.Telemetry;
+using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.Install.InstallSteps
{
@@ -13,31 +16,29 @@ namespace Umbraco.Cms.Core.Install.InstallSteps
PerformsAppRestart = false)]
public class TelemetryIdentifierStep : InstallSetupStep