diff --git a/Directory.Packages.props b/Directory.Packages.props index fe1b24d22d..5bcf6f8e8a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -72,7 +72,7 @@ - + diff --git a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj index 1dcba40ab6..260c5bec53 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj +++ b/src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs index ef9b9443ae..4652c513a3 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Locking/SqlServerEFCoreDistributedLockingMechanism.cs @@ -136,9 +136,7 @@ internal class SqlServerEFCoreDistributedLockingMechanism : IDistributedLocki "A transaction with minimum ReadCommitted isolation level is required."); } - await dbContext.Database.ExecuteSqlRawAsync($"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};"); - - var number = await dbContext.Database.ExecuteScalarAsync($"SELECT value FROM dbo.umbracoLock WITH (REPEATABLEREAD) WHERE id={LockId}"); + var number = await dbContext.Database.ExecuteScalarAsync($"SET LOCK_TIMEOUT {(int)_timeout.TotalMilliseconds};SELECT value FROM dbo.umbracoLock WITH (REPEATABLEREAD) WHERE id={LockId}"); if (number == null) { diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs index cc2e1b5feb..77975e8f31 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs @@ -143,9 +143,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism const string query = "SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id"; - db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";"); + var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}"; - var i = db.ExecuteScalar(query, new { id = LockId }); + // execute the lock timeout query and the actual query in a single server roundtrip + var i = db.ExecuteScalar($"{lockTimeoutQuery};{query}", new { id = LockId }); if (i == null) { @@ -178,9 +179,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism const string query = @"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id"; - db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";"); + var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}"; - var i = db.Execute(query, new { id = LockId }); + // execute the lock timeout query and the actual query in a single server roundtrip + var i = db.Execute($"{lockTimeoutQuery};{query}", new { id = LockId }); if (i == 0) { diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs index a29a4d1419..54e30d6fa6 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs @@ -162,7 +162,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism try { - var i = command.ExecuteNonQuery(); + var i = db.ExecuteNonQuery(command); if (i == 0) { diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index 54ebde6f57..c7fc343787 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -16,6 +16,9 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable private readonly IOptions _options; private readonly IHostEnvironment? _hostEnvironment; private readonly ISet _keys = new HashSet(); + private static readonly TimeSpan _readLockTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan _writeLockTimeout = TimeSpan.FromSeconds(5); + private readonly ReaderWriterLockSlim _locker = new(LockRecursionPolicy.SupportsRecursion); private bool _disposedValue; @@ -54,8 +57,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable Lazy? result; try { - _locker.EnterReadLock(); - + if (_locker.TryEnterReadLock(_readLockTimeout) is false) + { + throw new TimeoutException("Timeout exceeded to the memory cache when getting item"); + } result = MemoryCache.Get(key) as Lazy; // null if key not found } finally @@ -201,7 +206,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable { try { - _locker.EnterWriteLock(); + if (_locker.TryEnterWriteLock(_writeLockTimeout) is false) + { + throw new TimeoutException("Timeout exceeded to the memory cache when clearing item"); + } MemoryCache.Remove(key); _keys.Remove(key); @@ -286,7 +294,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable { try { - _locker.EnterWriteLock(); + if (_locker.TryEnterWriteLock(_writeLockTimeout) is false) + { + throw new TimeoutException("Timeout exceeded to the memory cache."); + } // ToArray required to remove foreach (var key in _keys.Where(predicate).ToArray()) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml index 1e76e92382..c91dabbad9 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml @@ -459,9 +459,10 @@ Ydych chi'n sicr? Ydych chi'n sicr? Torri - Golygu Eitem Geiriadur - Golygu Iaith + Golygu Eitem Geiriadur + Golygu Iaith Golygu cyfrwng a dewiswyd + Golygu bachyn gwe Mewnosod dolen leol Mewnosod nod Mewnosod pennawd graffig @@ -504,6 +505,7 @@ Agor y ddolen ddogfen mewn ffenestr neu tab newydd Dolen i gyfrwng Dewis nod cychwyn cynnwys + Dewis digwyddiad Dewis cyfrwng Dewis y math o gyfrwng Dewis eicon @@ -857,6 +859,7 @@ Gwybodaeth Umbraco Neidio i'r dewislen Neidio i'r cynnwys + Prif Glas @@ -1061,6 +1064,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Mewngofnodwch isod Mewngofnodwch gyda Sesiwn wedi cyrraedd terfyn amser + Wps! Mae mewngofnodi wedi methu. Gwiriwch eich manylion a thrio eto. © 2001 - %0%
Umbraco.com

]]>
Wedi anghofio eich cyfrinair? Bydd ebost yn cael ei anfon i'r cyfeiriad darparwyd gyda dolen i ailosod eich cyfrinair @@ -1424,6 +1428,44 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Gallwch ond ddewis eitemau o'r math(au): %0% Rydych wedi dewis eitem gynnwys sydd naill ai wedi'i ddileu neu yn y bin ailgylchu Rydych wedi dewis eitemau gynnwys sydd naill ai wedi'u dileu neu yn y bin ailgylchu + Nodi gwraidd + Dewiswch nod gwraidd + Nodi gwraidd efo XPath + Nodi Gwraidd Dynamig + Nod cychwyn + Ymholiad XPath + + + Ymholiad Gwraidd Dynamig + Dewiswch darddiad + Diffiniwch darddiad eich Ymholiad Gwraidd Dynamig + Gwraidd + Nod gwraidd y sesiwn olygu hon + Rhiant + Nod rhiant y ffynhonnell yn y sesiwn olygu hon + Cyfredol + Y nod cynnwys sy'n ffynhonnell ar gyfer y sesiwn olygu hon + Gwefan + Canfod nod agosaf gydag enw gwesteiwr + Nod penodol + Dewiswch Nod penodol fel tarddiad yr ymholiad hwn + Atodwch y cam i'r ymholiad + Diffiniwch y cam nesaf yn eich Ymholiad Gwraidd Dynamig + Hynafiad Agosaf Neu Hunan + Holwch yr hynafiad agosaf neu hunan sy'n cyd-fynd ag un o'r mathau sydd wedi'u ffurfweddu + Hynafiad Pellaf Neu Hunan + Holwch yr hynafiad pellaf neu'r hunan sy'n cyd-fynd ag un o'r mathau sydd wedi'u ffurfweddu + Disgynnydd Agosaf Neu Hunan + Holwch y disgynnydd agosaf neu'r hunan sy'n cyd-fynd ag un o'r mathau sydd wedi'u ffurfweddu + Disgynnydd Pellaf Neu Hunan + Holwch y disgynnydd pellaf neu'r hunan sy'n cyd-fynd ag un o'r mathau sydd wedi'u ffurfweddu + Arferiad + Ymholiad gan ddefnyddio Cam Ymholiad arferiad + Ychwanegu cam ymholiad + Sy'n cyfateb i'r mathau: + Dim cynnwys cyfatebol + Nid yw ffurfweddiad yr eiddo hwn yn cyfateb i unrhyw gynnwys. Creu'r cynnwys coll neu cysylltwch â'ch gweinyddwr i addasu gosodiadau Gwraidd Dynamig ar gyfer yr eiddo hwn. + Canslo a chlirio ymholiad Rydych wedi dewis eitem gyfrwng sydd naill ai wedi'i ddileu neu yn y bin ailgylchu @@ -1629,6 +1671,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Digwyddodd gwall wrth analluogi glanhau fersiwn ar gyfer %0% Mae gwybodaeth eich system wedi'i chopïo'n llwyddiannus i'r clipfwrdd Methu â chopïo gwybodaeth eich system i'r clipfwrdd + Bachyn gwe wedi arbed Ychwanegu ardull @@ -1873,6 +1916,33 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang NODYN! Mae glanhau fersiynau cynnwys hanesyddol wedi'u hanalluogi'n fyd-eang. Ni fydd y gosodiadau hyn yn dod i rym cyn iddo gael ei alluogi. Mae newid math o ddata gyda gwerthoedd storio wedi'i analluogi. I ganiatáu hyn gallwch newid y gosodiad Umbraco:CMS:DataTypes:CanBeChanged yn appssettings.json. + + Creu bachyn gwe + Ychwanegu pennyn bachyn gwe + Ychwanegu Math o Ddogfen + Ychwanegu Math o Gyfrwng + Creu pennawd + Danfoniadau + Nid oes penawdau bachyn gwe wedi'u hychwanegu + Ni chanfuwyd unrhyw ddigwyddiadau. + Wedi'i alluogi + DigwyddiadauD + Digwyddiad + Url + Mathau + Allwedd bachyn gwe + Cyfrif o Ailgeision + Togl modd dadfygio am ragor o wybodaeth. + Cod statws ddim yn OK + Yr url i'w alw pan fydd y bachyn gwe yn cael ei sbarduno. + Y digwyddiadau y dylid sbarduno'r bachyn gwe. + Sbardunwch y bachyn gwe am fath penodol o gynnwys yn unig. + A yw'r bachyn gwe wedi'i alluogi? + Penawdau arferu i'w cynnwys yn y cais bachyn gwe. + Math o Gynnwys + Penawdau + Dewiswch ddigwyddiad yn gyntaf. + Ychwanegu iaith Iath gorfodol @@ -2011,6 +2081,7 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Gosodiadau Templedi Trydydd parti + Bachau gwe Diweddariad newydd yn barod @@ -2769,6 +2840,8 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Ffurfweddu ardal Dileu ardal Ychwanegu opsiwn rhychwantu %0% colofn + Mewnosod Bloc + Arddangos yn fewnol â'r testun Beth yw Templedi Gynnwys diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 0000f92695..b344ed784a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -336,6 +336,7 @@ Send for approval is not allowed Schedule is not allowed Unpublish is not allowed + Select all variants %0%]]> diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 608c6943ea..58a6f8b704 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -334,6 +334,7 @@ Send for approval is not allowed Schedule is not allowed Unpublish is not allowed + Select all variants %0%]]> diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 7d512fbb2b..3ddb98b40e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -181,7 +181,7 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe return source; } - public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; + public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; public PropertyCacheLevel GetDeliveryApiPropertyCacheLevelForExpansion(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; diff --git a/src/Umbraco.Core/Scoping/LockingMechanism.cs b/src/Umbraco.Core/Scoping/LockingMechanism.cs index e41fe2d874..1fa779d221 100644 --- a/src/Umbraco.Core/Scoping/LockingMechanism.cs +++ b/src/Umbraco.Core/Scoping/LockingMechanism.cs @@ -13,8 +13,7 @@ public class LockingMechanism : ILockingMechanism { private readonly IDistributedLockingMechanismFactory _distributedLockingMechanismFactory; private readonly ILogger _logger; - private readonly object _lockQueueLocker = new(); - private readonly object _dictionaryLocker = new(); + private readonly object _locker = new(); private StackQueue<(DistributedLockType lockType, TimeSpan timeout, Guid instanceId, int lockId)>? _queuedLocks; private HashSet? _readLocks; private Dictionary>? _readLocksDictionary; @@ -35,12 +34,12 @@ public class LockingMechanism : ILockingMechanism } /// - public void ReadLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) => LazyReadLockInner(instanceId, timeout, lockIds); + public void ReadLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) => EagerReadLockInner(instanceId, timeout, lockIds); public void ReadLock(Guid instanceId, params int[] lockIds) => ReadLock(instanceId, null, lockIds); /// - public void WriteLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) => LazyWriteLockInner(instanceId, timeout, lockIds); + public void WriteLock(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) => EagerReadLockInner(instanceId, timeout, lockIds); public void WriteLock(Guid instanceId, params int[] lockIds) => WriteLock(instanceId, null, lockIds); @@ -64,7 +63,7 @@ public class LockingMechanism : ILockingMechanism /// Array of lock object identifiers. private void EagerWriteLockInner(Guid instanceId, TimeSpan? timeout, params int[] lockIds) { - lock (_dictionaryLocker) + lock (_locker) { foreach (var lockId in lockIds) { @@ -106,7 +105,7 @@ public class LockingMechanism : ILockingMechanism /// Array of lock object identifiers. private void EagerReadLockInner(Guid instanceId, TimeSpan? timeout, params int[] lockIds) { - lock (_dictionaryLocker) + lock (_locker) { foreach (var lockId in lockIds) { @@ -219,7 +218,7 @@ public class LockingMechanism : ILockingMechanism private void LazyLockInner(DistributedLockType lockType, Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) { - lock (_lockQueueLocker) + lock (_locker) { if (_queuedLocks == null) { @@ -239,7 +238,7 @@ public class LockingMechanism : ILockingMechanism /// Instance ID of the scope to clear. public void ClearLocks(Guid instanceId) { - lock (_dictionaryLocker) + lock (_locker) { _readLocksDictionary?.Remove(instanceId); _writeLocksDictionary?.Remove(instanceId); @@ -294,7 +293,7 @@ public class LockingMechanism : ILockingMechanism /// public void EnsureLocks(Guid scopeInstanceId) { - lock (_lockQueueLocker) + lock (_locker) { if (!(_queuedLocks?.Count > 0)) { diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index 74faacabd0..0f12e5cbdd 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -1,5 +1,8 @@ using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; @@ -9,27 +12,33 @@ namespace Umbraco.Cms.Core.Services; public class PropertyValidationService : IPropertyValidationService { private readonly IDataTypeService _dataTypeService; + private readonly ILocalizedTextService _textService; private readonly PropertyEditorCollection _propertyEditors; private readonly IValueEditorCache _valueEditorCache; + private readonly ICultureDictionary _cultureDictionary; - [Obsolete($"Use the constructor that does not accept {nameof(ILocalizedTextService)}. Will be removed in V15.")] + [Obsolete("Use the constructor that accepts ICultureDictionary. Will be removed in V15.")] public PropertyValidationService( PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService, IValueEditorCache valueEditorCache) - : this(propertyEditors, dataTypeService, valueEditorCache) + : this(propertyEditors, dataTypeService, textService, valueEditorCache, StaticServiceProvider.Instance.GetRequiredService()) { } public PropertyValidationService( PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, - IValueEditorCache valueEditorCache) + ILocalizedTextService textService, + IValueEditorCache valueEditorCache, + ICultureDictionary cultureDictionary) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; + _textService = textService; _valueEditorCache = valueEditorCache; + _cultureDictionary = cultureDictionary; } /// @@ -80,13 +89,13 @@ public class PropertyValidationService : IPropertyValidationService if (isRequired && !string.IsNullOrWhiteSpace(isRequiredMessage) && requiredDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) { - validationResult.ErrorMessage = isRequiredMessage; + validationResult.ErrorMessage = _textService.UmbracoDictionaryTranslate(_cultureDictionary, isRequiredMessage); } if (!string.IsNullOrWhiteSpace(validationRegExp) && !string.IsNullOrWhiteSpace(validationRegExpMessage) && formatDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) { - validationResult.ErrorMessage = validationRegExpMessage; + validationResult.ErrorMessage = _textService.UmbracoDictionaryTranslate(_cultureDictionary, validationRegExpMessage); } yield return validationResult; diff --git a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs index a3508a3fed..c572fc85f7 100644 --- a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs @@ -1,9 +1,9 @@ +using System.Globalization; using Examine; using Examine.Lucene; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; @@ -125,8 +125,14 @@ public class DeliveryApiContentIndex : UmbracoExamineIndex private (string? ContentId, string? Culture) ParseItemId(string id) { + if (int.TryParse(id, out _)) + { + return (id, null); + } + DeliveryApiIndexCompositeIdModel compositeIdModel = _deliveryApiCompositeIdHandler.Decompose(id); - return (compositeIdModel.Id.ToString() ?? id, compositeIdModel.Culture); + + return (compositeIdModel.Id?.ToString(CultureInfo.InvariantCulture), compositeIdModel.Culture); } protected override void OnTransformingIndexValues(IndexingItemEventArgs e) diff --git a/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs index 431ddeb5e8..b86648f05c 100644 --- a/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs +++ b/src/Umbraco.Infrastructure/Persistence/IUmbracoDatabase.cs @@ -1,3 +1,4 @@ +using System.Data.Common; using NPoco; using Umbraco.Cms.Infrastructure.Migrations.Install; @@ -33,4 +34,7 @@ public interface IUmbracoDatabase : IDatabase bool IsUmbracoInstalled(); DatabaseSchemaResult ValidateSchema(); + + /// The number of rows affected. + int ExecuteNonQuery(DbCommand command) => command.ExecuteNonQuery(); } diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs index af8eb8e1fe..3abec76ec2 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs @@ -223,6 +223,14 @@ public class UmbracoDatabase : Database, IUmbracoDatabase return databaseSchemaValidationResult ?? new DatabaseSchemaResult(); } + public int ExecuteNonQuery(DbCommand command) + { + OnExecutingCommand(command); + var i = command.ExecuteNonQuery(); + OnExecutedCommand(command); + return i; + } + /// /// Returns true if Umbraco database tables are detected to be installed /// diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index f38b9dd2fc..0230032dc2 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -27,6 +27,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache; /// public class ContentStore { + private static readonly TimeSpan _monitorTimeout = TimeSpan.FromSeconds(30); + // TODO: collection trigger (ok for now) // see SnapDictionary notes private const long CollectMinGenDelta = 8; @@ -330,7 +332,12 @@ public class ContentStore throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.Enter(_wlocko, ref lockInfo.Taken); + Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); + + if (Monitor.IsEntered(_wlocko) is false) + { + throw new TimeoutException("Could not enter monitor before timeout in content store"); + } lock (_rlocko) { diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs index 09855f5682..13e911f137 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs @@ -127,9 +127,25 @@ public class NuCacheContentService : RepositoryService, INuCacheContentService { using (ICoreScope scope = ScopeProvider.CreateCoreScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) { - scope.ReadLock(Constants.Locks.ContentTree); - scope.ReadLock(Constants.Locks.MediaTree); - scope.ReadLock(Constants.Locks.MemberTree); + if (contentTypeIds is null && mediaTypeIds is null && memberTypeIds is null) + { + scope.ReadLock(Constants.Locks.ContentTree,Constants.Locks.MediaTree,Constants.Locks.MemberTree); + } + + if (contentTypeIds is not null && contentTypeIds.Any()) + { + scope.ReadLock(Constants.Locks.ContentTree); + } + + if (mediaTypeIds is not null && mediaTypeIds.Any()) + { + scope.ReadLock(Constants.Locks.MediaTree); + } + + if (memberTypeIds is not null && memberTypeIds.Any()) + { + scope.ReadLock(Constants.Locks.MemberTree); + } _repository.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds); diff --git a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs index 0d042380d2..b6c87e22bb 100644 --- a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs +++ b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs @@ -9,6 +9,8 @@ public class SnapDictionary where TValue : class where TKey : notnull { + private static readonly TimeSpan _monitorTimeout = TimeSpan.FromSeconds(30); + // minGenDelta to be adjusted // we may want to throttle collects even if delta is reached // we may want to force collect if delta is not reached but very old @@ -198,7 +200,12 @@ public class SnapDictionary throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.Enter(_wlocko, ref lockInfo.Taken); + Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); + + if (Monitor.IsEntered(_wlocko) is false) + { + throw new TimeoutException("Could not enter the monitor before timeout in SnapDictionary"); + } lock (_rlocko) { diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client deleted file mode 160000 index 2cc9da721e..0000000000 --- a/src/Umbraco.Web.UI.Client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2cc9da721ee7002cba26c2a4aae1f49d46f973fb diff --git a/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts b/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts index 4725d8148d..e1f51ee5ef 100644 --- a/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts +++ b/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts @@ -25,14 +25,7 @@ export class UmbAuthRepository { }); const response = await fetch(request); - const responseData: LoginResponse = { - status: response.status, - }; - - if (!response.ok) { - responseData.error = await this.#getErrorText(response); - return responseData; - } + let responseData: any = undefined; // Additionally authenticate with the Management API await this.#managementApiLogin(data.username, data.password); @@ -40,14 +33,15 @@ export class UmbAuthRepository { try { const text = await response.text(); if (text) { - responseData.data = JSON.parse(this.#removeAngularJSResponseData(text)); + responseData = JSON.parse(this.#removeAngularJSResponseData(text)); } } catch { } return { status: response.status, - data: responseData?.data, + error: response.ok ? undefined : await this.#getErrorText(response), + data: responseData, twoFactorView: responseData?.twoFactorView, }; } catch (error) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs index 21bffb23aa..40c2ab7915 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -1250,7 +1251,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent Assert.IsFalse(content.HasIdentity); // content cannot publish values because they are invalid - var propertyValidationService = new PropertyValidationService(PropertyEditorCollection, DataTypeService, ValueEditorCache); + var propertyValidationService = new PropertyValidationService(PropertyEditorCollection, DataTypeService, ValueEditorCache, Mock.Of()); var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties, CultureImpact.Invariant); Assert.IsFalse(isValid); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs index da3ba0f298..8387aaeb7a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; @@ -14,7 +12,6 @@ using Umbraco.Cms.Persistence.Sqlite.Interceptors; using Umbraco.Cms.Tests.Common.Attributes; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence; @@ -125,6 +122,7 @@ public class LocksTests : UmbracoIntegrationTest } } + [NUnit.Framework.Ignore("We currently do not have a way to force lazy locks")] [Test] public void GivenNonEagerLocking_WhenNoDbIsAccessed_ThenNoSqlIsExecuted() { @@ -154,6 +152,37 @@ public class LocksTests : UmbracoIntegrationTest Assert.AreEqual(0, sqlCount); } + [Test] + public void GivenNonEagerLocking_WhenDbIsAccessed_ThenSqlIsExecuted() + { + var sqlCount = 0; + + using (var scope = ScopeProvider.CreateScope()) + { + var db = ScopeAccessor.AmbientScope.Database; + try + { + db.EnableSqlCount = true; + + // Issue a lock request, but we are using non-eager + // locks so this only queues the request. + // The lock will not be issued unless we resolve + // scope.Database + scope.WriteLock(Constants.Locks.Servers); + + scope.Database.ExecuteScalar("SELECT 1"); + + sqlCount = db.SqlCount; + } + finally + { + db.EnableSqlCount = false; + } + } + + Assert.AreEqual(2,sqlCount); + } + [Test] [LongRunning] public void ConcurrentWritersTest() diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs index 2598f0a559..79d247c118 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs @@ -5,6 +5,7 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; @@ -648,6 +649,7 @@ public class VariationTests return new PropertyValidationService( propertyEditorCollection, dataTypeService, - new ValueEditorCache()); + new ValueEditorCache(), + Mock.Of()); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs index 4bf81cd88d..cd1e128e4a 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs @@ -5,6 +5,7 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; @@ -43,7 +44,7 @@ public class PropertyValidationServiceTests var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor })); - validationService = new PropertyValidationService(propEditors, dataTypeService.Object, new ValueEditorCache()); + validationService = new PropertyValidationService(propEditors, dataTypeService.Object, new ValueEditorCache(), Mock.Of()); } [Test]