Compare commits

...
Author SHA1 Message Date
Laura Neto 751d70d45e HybridCache: Add ReadLock to prevent potential SQL Server deadlocks
Add ReadLock to database read operations in HybridCache services to prevent
deadlocks during concurrent content/media save operations. This extends the
fix from PR #21156 to other methods that were missing lock protection.

Methods updated with ReadLock:
- DocumentCacheService: GetNodeAsync, GetByContentType, SeedAsync, RebuildMemoryCacheByContentTypeAsync
- MediaCacheService: GetNodeAsync, GetByContentType, SeedAsync, RefreshMemoryCacheAsync, RebuildMemoryCacheByContentTypeAsync
- ContentTypeSeedKeyProvider: GetSeedKeys
2026-01-05 17:35:15 +01:00
3 changed files with 11 additions and 0 deletions
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Navigation;
@@ -28,6 +29,7 @@ internal sealed class ContentTypeSeedKeyProvider : IDocumentSeedKeyProvider
public ISet<Guid> GetSeedKeys()
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.ContentTree);
var documentKeys = _databaseCacheRepository
.GetDocumentKeysByContentTypeKeys(_cacheSettings.ContentTypeKeys, published: true)
.Where(key => _publishStatusService.IsDocumentPublishedInAnyCulture(key))
@@ -121,6 +121,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
async cancel =>
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.ContentTree);
ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(key, preview);
// If we can resolve the content cache node, we still need to check if the ancestor path is published.
@@ -159,6 +160,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
public IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.ContentTree);
IEnumerable<ContentCacheNode> nodes = _databaseCacheRepository.GetContentByContentTypeKey([contentType.Key], ContentCacheDataSerializerEntityType.Document);
scope.Complete();
@@ -237,6 +239,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
}
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.ContentTree);
IEnumerable<ContentCacheNode> cacheNodes = await _databaseCacheRepository.GetContentSourcesAsync(uncachedKeys);
@@ -354,6 +357,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable<int> contentTypeIds)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.ContentTree);
IEnumerable<ContentCacheNode> contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(contentTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.DocumentType).Result), ContentCacheDataSerializerEntityType.Document);
scope.Complete();
@@ -117,6 +117,7 @@ internal sealed class MediaCacheService : IMediaCacheService
async cancel =>
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.MediaTree);
ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(key);
scope.Complete();
return mediaCacheNode;
@@ -201,6 +202,7 @@ internal sealed class MediaCacheService : IMediaCacheService
}
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.MediaTree);
IEnumerable<ContentCacheNode> cacheNodes = await _databaseCacheRepository.GetMediaSourcesAsync(uncachedKeys);
@@ -231,6 +233,7 @@ internal sealed class MediaCacheService : IMediaCacheService
public async Task RefreshMemoryCacheAsync(Guid key)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.MediaTree);
ContentCacheNode? publishedNode = await _databaseCacheRepository.GetMediaSourceAsync(key);
if (publishedNode is not null)
@@ -257,6 +260,7 @@ internal sealed class MediaCacheService : IMediaCacheService
public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable<int> mediaTypeIds)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.MediaTree);
IEnumerable<ContentCacheNode> contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(mediaTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.MediaType).Result), ContentCacheDataSerializerEntityType.Media);
@@ -301,6 +305,7 @@ internal sealed class MediaCacheService : IMediaCacheService
public IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
scope.ReadLock(Constants.Locks.MediaTree);
IEnumerable<ContentCacheNode> nodes = _databaseCacheRepository.GetContentByContentTypeKey([contentType.Key], ContentCacheDataSerializerEntityType.Media);
scope.Complete();