Skip to content

Commit 4351ce6

Browse files
author
Paul Johnson
authored
Further changes requested during review of #12049 (#12053)
1 parent de4b3af commit 4351ce6

File tree

5 files changed

+45
-55
lines changed

5 files changed

+45
-55
lines changed

src/Umbraco.Core/Configuration/Models/GlobalSettings.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class GlobalSettings
2929
internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml";
3030
internal const string StaticSqlWriteLockTimeOut = "00:00:05";
3131
internal const bool StaticSanitizeTinyMce = false;
32+
internal const int StaticMainDomReleaseSignalPollingInterval = 2000;
3233

3334
/// <summary>
3435
/// Gets or sets a value for the reserved URLs (must end with a comma).
@@ -145,6 +146,18 @@ public class GlobalSettings
145146
/// </summary>
146147
public string MainDomKeyDiscriminator { get; set; } = string.Empty;
147148

149+
/// <summary>
150+
/// Gets or sets the duration (in milliseconds) for which the MainDomLock release signal polling task should sleep.
151+
/// </summary>
152+
/// <remarks>
153+
/// Doesn't apply to MainDomSemaphoreLock.
154+
/// <para>
155+
/// The default value is 2000ms.
156+
/// </para>
157+
/// </remarks>
158+
[DefaultValue(StaticMainDomReleaseSignalPollingInterval)]
159+
public int MainDomReleaseSignalPollingInterval { get; set; } = StaticMainDomReleaseSignalPollingInterval;
160+
148161
/// <summary>
149162
/// Gets or sets the telemetry ID.
150163
/// </summary>

src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ private static IUmbracoBuilder AddMainDom(this IUmbracoBuilder builder)
234234

235235
if (globalSettings.Value.MainDomLock == "FileSystemMainDomLock")
236236
{
237-
return new FileSystemMainDomLock(loggerFactory.CreateLogger<FileSystemMainDomLock>(), mainDomKeyGenerator, hostingEnvironment);
237+
return new FileSystemMainDomLock(loggerFactory.CreateLogger<FileSystemMainDomLock>(), mainDomKeyGenerator, hostingEnvironment, factory.GetRequiredService<IOptionsMonitor<GlobalSettings>>());
238238
}
239239

240240
return globalSettings.Value.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
Lines changed: 22 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Diagnostics;
43
using System.IO;
54
using System.Threading;
65
using System.Threading.Tasks;
76
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Options;
8+
using Umbraco.Cms.Core.Configuration.Models;
89
using Umbraco.Cms.Core.Hosting;
910
using Umbraco.Cms.Core.Runtime;
1011

@@ -13,21 +14,22 @@ namespace Umbraco.Cms.Infrastructure.Runtime
1314
internal class FileSystemMainDomLock : IMainDomLock
1415
{
1516
private readonly ILogger<FileSystemMainDomLock> _logger;
17+
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
1618
private readonly CancellationTokenSource _cancellationTokenSource = new();
1719
private readonly string _lockFilePath;
1820
private readonly string _releaseSignalFilePath;
1921

2022
private FileStream _lockFileStream;
2123
private Task _listenForReleaseSignalFileTask;
2224

23-
private const int s_maxTriesRemovingLockReleaseSignalFile = 3;
24-
2525
public FileSystemMainDomLock(
2626
ILogger<FileSystemMainDomLock> logger,
2727
IMainDomKeyGenerator mainDomKeyGenerator,
28-
IHostingEnvironment hostingEnvironment)
28+
IHostingEnvironment hostingEnvironment,
29+
IOptionsMonitor<GlobalSettings> globalSettings)
2930
{
3031
_logger = logger;
32+
_globalSettings = globalSettings;
3133

3234
var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock";
3335
_lockFilePath = Path.Combine(hostingEnvironment.LocalTempPath, lockFileName);
@@ -45,18 +47,18 @@ public Task<bool> AcquireLockAsync(int millisecondsTimeout)
4547
{
4648
_logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
4749
_lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
48-
DeleteLockReleaseFile();
50+
DeleteLockReleaseSignalFile();
4951
return Task.FromResult(true);
5052
}
5153
catch (IOException)
5254
{
5355
_logger.LogDebug("Couldn't obtain MainDom lock file handle, signalling for release of {lockFilePath}", _lockFilePath);
54-
CreateLockReleaseFile();
55-
Thread.Sleep(500);
56+
CreateLockReleaseSignalFile();
5657
}
5758
catch (Exception ex)
5859
{
5960
_logger.LogError(ex, "Unexpected exception attempting to obtain MainDom lock file handle {lockFilePath}, giving up", _lockFilePath);
61+
_lockFileStream?.Close();
6062
return Task.FromResult(false);
6163
}
6264
}
@@ -65,6 +67,12 @@ public Task<bool> AcquireLockAsync(int millisecondsTimeout)
6567
return Task.FromResult(false);
6668
}
6769

70+
public void CreateLockReleaseSignalFile() =>
71+
_ = File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
72+
73+
public void DeleteLockReleaseSignalFile() =>
74+
File.Delete(_releaseSignalFilePath);
75+
6876
// Create a long running task to poll to check if anyone has created a lock release file.
6977
public Task ListenAsync()
7078
{
@@ -82,46 +90,6 @@ public Task ListenAsync()
8290
return _listenForReleaseSignalFileTask;
8391
}
8492

85-
public void Dispose()
86-
{
87-
_lockFileStream?.Close();
88-
_lockFileStream = null;
89-
}
90-
91-
private void CreateLockReleaseFile()
92-
{
93-
try
94-
{
95-
// Dispose immediately to release the file handle so it's easier to cleanup in any process.
96-
using FileStream releaseFileStream = File.Open(_releaseSignalFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
97-
}
98-
catch (Exception ex)
99-
{
100-
_logger.LogError(ex, "Unexpected exception attempting to create lock release signal file {file}", _releaseSignalFilePath);
101-
}
102-
}
103-
104-
private void DeleteLockReleaseFile()
105-
{
106-
List<Exception> encounteredExceptions = new();
107-
for (var i = 0; i < s_maxTriesRemovingLockReleaseSignalFile; i++)
108-
{
109-
try
110-
{
111-
File.Delete(_releaseSignalFilePath);
112-
return;
113-
}
114-
catch (Exception ex)
115-
{
116-
_logger.LogError(ex, "Unexpected exception attempting to delete release signal file {file}", _releaseSignalFilePath);
117-
encounteredExceptions.Add(ex);
118-
Thread.Sleep(500 * (i + 1));
119-
}
120-
}
121-
122-
throw new ApplicationException($"Failed to remove lock release signal file {_releaseSignalFilePath}", new AggregateException(encounteredExceptions));
123-
}
124-
12593
private void ListeningLoop()
12694
{
12795
while (true)
@@ -140,8 +108,14 @@ private void ListeningLoop()
140108
break;
141109
}
142110

143-
Thread.Sleep(2000);
111+
Thread.Sleep(_globalSettings.CurrentValue.MainDomReleaseSignalPollingInterval);
144112
}
145113
}
114+
115+
public void Dispose()
116+
{
117+
_lockFileStream?.Close();
118+
_lockFileStream = null;
119+
}
146120
}
147121
}

src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ private void ListeningLoop()
236236
{
237237
// poll every couple of seconds
238238
// local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO
239-
Thread.Sleep(2000);
239+
Thread.Sleep(_globalSettings.Value.MainDomReleaseSignalPollingInterval);
240240

241241
if (!_dbFactory.Configured)
242242
{

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Runtime/FileSystemMainDomLockTests.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System.IO;
22
using System.Threading.Tasks;
33
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Options;
5+
using Moq;
46
using NUnit.Framework;
7+
using Umbraco.Cms.Core.Configuration.Models;
58
using Umbraco.Cms.Core.Hosting;
69
using Umbraco.Cms.Core.Runtime;
710
using Umbraco.Cms.Infrastructure.Runtime;
@@ -31,8 +34,11 @@ public void SetUp()
3134
LockFilePath = Path.Combine(HostingEnvironment.LocalTempPath, lockFileName);
3235
LockReleaseFilePath = LockFilePath + "_release";
3336

37+
var globalSettings = Mock.Of<IOptionsMonitor<GlobalSettings>>();
38+
Mock.Get(globalSettings).Setup(x => x.CurrentValue).Returns(new GlobalSettings());
39+
3440
var log = GetRequiredService<ILogger<FileSystemMainDomLock>>();
35-
FileSystemMainDomLock = new FileSystemMainDomLock(log, MainDomKeyGenerator, HostingEnvironment);
41+
FileSystemMainDomLock = new FileSystemMainDomLock(log, MainDomKeyGenerator, HostingEnvironment, globalSettings);
3642
}
3743

3844
[TearDown]
@@ -79,10 +85,7 @@ public async Task ListenAsync_WhenLockReleaseSignalFileFound_DropsLockFileHandle
7985

8086
var before = await sut.AcquireLockAsync(1000);
8187

82-
await using (_ = File.Open(LockReleaseFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
83-
{
84-
}
85-
88+
sut.CreateLockReleaseSignalFile();
8689
await sut.ListenAsync();
8790

8891
var after = await sut.AcquireLockAsync(1000);

0 commit comments

Comments
 (0)