I think using IContentSoftLinkRepository is a better way to get content references?
Thanks for the suggestion.
I did a try to use IContentSoftLinkRepository.
Still got the same/similar issue:
System.AggregateException: One or more errors occurred. (Call on database executor not created on current context and on different thread. Possible rooted IDatabaseExecutor, which is not thread safe.)
---> System.InvalidOperationException: Call on database executor not created on current context and on different thread. Possible rooted IDatabaseExecutor, which is not thread safe.
at EPiServer.Data.Internal.ConnectionScopeResolver.AssertThreadSafe()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.CreateCommand()
at EPiServer.DataAccess.DataAccessBase.CreateCommand()
at EPiServer.DataAccess.Internal.ContentCoreDataDB.<>c__DisplayClass4_0.<LoadInternal>b__0()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.<>c__DisplayClass24_0`1.<Execute>b__0()
at EPiServer.Data.Providers.SqlTransientErrorsRetryPolicy.Execute[TResult](Func`1 method)
at EPiServer.Core.Internal.DefaultContentProvider.ResolveContent(ContentReference contentLink)
at EPiServer.Web.Internal.PermanentContentLinkMapper.FindInternal(ContentReference contentLink)
at EPiServer.Web.Internal.PermanentLinkMapper.Find(ContentReference contentReference)
at EPiServer.DataAbstraction.Internal.DefaultContentSoftLinkRepository.Load(ContentReference contentLink, Boolean reversed)
IContentRepository is Singleton scoped and should be fine to root inside another singleton (with some exceptions that follows). The underlying IDatabaseExecutor is backed in http context (so it can be resued during transactions). So you might run into this issue if you in a single http request end up with several threads using the same scope, e.g. if you would have some code like:
var contentLinks = new[] {new ContentReference(1), new ContentReference(2), new ContentReference(3)};
Parallel.ForEach(contentLinks, link =>
{
contentRepository.GetDescendents(link);
});
Reason is that AsyncLocal "flows" from main thread to new thread, and hence HttpContext. The above code could be rewritten as
public class NoHttpContextScope : IDisposable
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly HttpContext _context;
public NoHttpContextScope(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_context = httpContextAccessor.HttpContext;
httpContextAccessor.HttpContext = null;
}
public void Dispose() => _httpContextAccessor.HttpContext = _context;
}
var contentLinks = new[] { new ContentReference(1), new ContentReference(2), new ContentReference(3), new ContentReference(4) };
using (var noHttpContextScope = new NoHttpContextScope(httpContextAccessor))
{
var result = Parallel.ForEach(contentLinks, link =>
{
contentRepository.GetDescendents(link);
});
}
So is your case that you spawn up new parallel threads within a http request, and that the exception is thrown within a new treads execution? Or is there another usecase, if so please provide more information, e.g. is the code within a http request or outside a http request (like a scehduled job)?
Hi!
Thank you for the great detailed reponse.
Our use-case when this problem arises is when we create a fire and forget task
inside our handler for contentEvents.PublishedContent
Task.Run(async () => await invalidationCacheService.InvalidateCacheByContentAsync(e.Content)
The method running is not spawning up new threads as we are aware of but the thread we are creating ofcourse is a new thread with a new id.
The reason for us creating a fire and forget task is since we dont want to block the UI while invalidating the cache.
By the way is there any easy way to check if you are withing a current HttpRequestContext or not?
Our initial thoughts on the problem was that the problem arises when different editors triggered the PublishedContent event at the same time.
But since you say that the context is backed by the HttpContext we are not so sure anymore?
If you inside you Task.Run logic run
httpContextAccessor.HttpContext = null;
it should solve your problem.
We'll keep the bug to see if we can improve this, but the above should be a workaround for now.
I'm also experiencing this issue on CMS 12 while running a scheduled job to update content programmatically.
Just noticed this on .NET 8 and all updated packages from a couple of weeks ago...
System.InvalidOperationException: Call on database executor not created on current context and on different thread. Possible rooted IDatabaseExecutor, which is not thread safe.
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.Execute[TResult](Func`1 action)
at EPiServer.Data.SchemaUpdates.Internal.DatabaseVersionRetriever.GetDatabaseVersion(Boolean forceRefresh)
at EPiServer.Cms.HealthCheck.Internal.CmsHealthCheck.CheckHealthAsync(IsolatedHealthCheckContext context, CancellationToken cancellationToken)
How often do you get the issue @Johan? Is it one time or once in a while, or almost always?
I will forward the question to the Content platform team
Hi,
I am also getting the same error; when I set the HTTP context null as suggested, this error is solved, but putting the HTTP context null causes another exception in the checkout flow. Is there another workaround for this, or will it be solved in EPi libraries or any other solution?
This was fixed in EPiServer.CMS.Core 12.16.0
See https://world.optimizely.com/documentation/Release-Notes/ReleaseNote/?releaseNoteId=CMS-23353
When using IContentRepository in a service injected by th constructor (Depencency injection).
Our service is registrated as Transient.
When calling:
_contentRepository.GetReferencesToContent(content.ContentLink, includeDecendents);
Sometimes we get the following stacktrace:
System.AggregateException: One or more errors occurred. (Call on database executor not created on current context and on different thread. Possible rooted IDatabaseExecutor, which is not thread safe.)
---> System.InvalidOperationException: Call on database executor not created on current context and on different thread. Possible rooted IDatabaseExecutor, which is not thread safe.
at EPiServer.Data.Internal.ConnectionScopeResolver.AssertThreadSafe()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.CreateCommand()
at EPiServer.DataAccess.Internal.ContentSaveDB.<>c__DisplayClass20_0.<GetReferenceInformationForContent>b__0()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.<>c__DisplayClass24_0`1.<Execute>b__0()
at EPiServer.Data.Providers.SqlTransientErrorsRetryPolicy.Execute[TResult](Func`1 method)
at EPiServer.DataAccess.Internal.ContentSaveDB.GetReferenceInformationForContent(ContentReference contentLink, Boolean includeDecendents)
at EPiServer.Core.Internal.DefaultContentProvider.GetReferencesToLocalContent(ContentReference contentLink, Boolean includeDecendents)
at EPiServer.Core.Internal.DefaultContentRepository.GetReferencesToContent(ContentReference contentLink, IEnumerable`1 descendents, Boolean includeDescendants)
Is it intended by design that the DbContext inside IContentRepository should not be thread-safe?
Version: 12.4.1
.Net 6.0