Fixing “Stackoverflow when compiling callsite chain on a background thread” in Sitecore
Let me tell you a story about a codebase that was using Autofac. It is a beast of a solution — a whopping 230 projects — a definite case of “when Helix goes wrong”. I was tasked in late 2018 to upgrade it from Sitecore 8.2 to 9.0.2 and one of the many things I wanted to do was to convert from Autofac to Microsoft Dependency Injection (hereafter referred to as MSDI).
The reasons for removing Autofac:
- 10 times slower than MSDI on most benchmarks you can find online
- Sitecore Support recommended we remove it after a few tickets we raised around random behaviour in places like the Path Analyser and List Manager
- We were getting some thread locking issues on startup with no apparent cause — again Sitecore Support said it was Autofac.
It was a fairly painful conversion — having all those projects made it repetivite to copy paste from the old Autofac dependency registrations to the MSDI way of doing it. Mostly there was an easy way of converting these, except when it came to a crazy bit of architecture where there was a Repository pattern wrapping Entity Framework. Without getting too stuck into details, the Repository looks something like this:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal DbContext _context;
internal DbSet<TEntity> _dbSet;
public GenericRepository(DbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
...
This GenericRepository is then registered like this in Autofac:
builder.RegisterType<ApplyContext>().Named<DbContext>("ApplyContext").InstancePerLifetimeScope();builder.RegisterGeneric(typeof(GenericRepository<>)).WithParameter((pi, c) => pi.ParameterType == typeof(DbContext), (pi, c) => c.ResolveNamed<DbContext>("ApplyContext")).Named("ApplyRepository", typeof(GenericRepository<>));
And used like this:
private readonly GenericRepository<Application> _applicationRepository;
private readonly GenericRepository<CourseRef> _courseRepository;
private readonly GenericRepository<CourseOfferingRef> _courseOfferingRepository;
To make it more painful, there are multiple DbContexts in the solution, so using these Named parameters is required.
Now, this all seemed a very odd thing to do as I’d never seen this pattern despite working extensively in .NET and EF for the last 10 years. Sure enough, a Google shows that wrapping EF with a Repository pattern is actually an antipattern.
A side note: I would never recommend using Entity Framework in a Sitecore solution. If you need it, then create a separate WebAPI with .NET Core and call it happily from Sitecore. There are just too many performance, DI, and maintenance issues in having EF in Sitecore.
Well, that doesn’t help me much as the project uses this extensively, and I only had a few weeks to do this upgrade. So after a bit of refactoring I managed to get the equivalent dependency registrations going in MSDI.
serviceCollection.AddScoped<DbContext, AcceptContext>();
serviceCollection.AddScoped<AcceptContext, AcceptContext>();
serviceCollection.AddScoped(typeof(AcceptRepository<>), typeof(AcceptRepository<>));
But, alas, I soon ran into this known issue in MSDI 1.x and 2.x. After investigating, it was because MSDI can’t build the dependency tree for some of these more complex scenarios that I had come across above.
Sitecore 9.0 uses MSDI 1.x, and 9.1 and 9.2 use MSDI 2.x. If you read through that GitHub thread, someone came up a with a workaround for MSDI 2.x which is to use reflection to change the Mode from “Dynamic” to “Runtime”.
However, there was no workaround available in MSDI 1.x which is what Sitecore 9.0.x supports. So I couldn’t see any way around this issue, and in the end we went back to using Autofac.
Now comes Sitecore 9.2 and MSDI 2.x
Finally, a year later, we are upgrading to Sitecore 9.2 which uses MSDI 2.2.1.
Let’s see if we can finally get rid of Autofac. I went back and got the bulk of the code changes from last year, spent a few days making some tweaks and updates to it from all the changes over the year, and fired it up.
No cigar — same issue as last year. Not to worry, now I can put in that workaround.
Yes!
Fixed, problem solved.
Final thoughts
The cost of bad architectural decisions and technical debt is not always easily quantifiable, but in this case it is very apparent as it has caused headaches all year. The amount of man hours of production support and Sitecore Support tickets is significant, not to mention all the time and effort it is to find a band-aid for this issue.
When starting on a new major feature or project, always get a rough solution validated with someone experienced with Sitecore, in this case splitting off the EF data access to a separate solution outside of Sitecore would have been an excellent decision so as to not increase the complexity of an already very complex system.