question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Memory/EF issues with the way queries are created

See original GitHub issue

We’ve been getting an issue where EF Core logs the following: More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. . We also noticed that after running for a short period of time the process was taking 6+Gb of RAM on our server.

We think we tracked it down to the fix for issue #9, i.e. this commit: https://github.com/BrighterCommand/Darker/commit/2a07b6831e96497da955cebe0cd0b05c804c40e4

Basically, it seems that creating all those service providers is causing EF Core issues.

Our fix works for us, although it may not work in every scenario.

Initially, what we did was in the ServiceCollectionExtensions.AddDarker method we changed the services.AddSingleton<IQueryProcessor> line to:

services.AddSingleton<IQueryProcessor>(sp =>
{
  factory.SetProvider(sp);
  var queryProcessor = builder.Build();
  return queryProcessor;
});

And added a SetProvider() method to the AspNetHandlerFactory class, which we had to extract and rename because it was internal.

public sealed class AcHandlerFactory : IQueryHandlerFactory, IQueryHandlerDecoratorFactory
{
  private IServiceProvider _services;
  public void SetProvider(IServiceProvider serviceProvider)
  {
    if (_services != null)
      throw new InvalidOperationException($"A service provider is already assigned to the {nameof(AcHandlerFactory)}.");
    _services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
  }

  IQueryHandler IQueryHandlerFactory.Create(Type handlerType)
  {
    if (_services == null)
      throw new InvalidOperationException($"A service provider must be assigned to the {nameof(AcHandlerFactory)} in advance of its use.");

    var result = (IQueryHandler)_services.GetService(handlerType);
    return result;
  }
// ... other things in the class
}

That fixed the initial issue… But since the service provider passed to the Singleton’s new factory function isn’t scoped, anything created that is scoped, say, something working inside a web request, causes the DI framework to break when calling GetService() on the IServiceProvider.

So… The next step was to ensure those didn’t break either. So, I now create a new scope for every query (we’ve done this kind of thing in Brighter before, and works well in back end applications such as windows services where there is no natural scope for a single piece of work)

That changes the handler factory to this:

   public sealed class AcHandlerFactory : IQueryHandlerFactory, IQueryHandlerDecoratorFactory
    {
        private IServiceProvider _services;
        private readonly Dictionary<IQueryHandler, IServiceScope> _scopes = new Dictionary<IQueryHandler, IServiceScope>();

         public AcHandlerFactory()
        {
        }

         public void SetProvider(IServiceProvider serviceProvider)
        {
            if (_services != null)
                throw new InvalidOperationException($"A service provider is already assigned to the {nameof(AcHandlerFactory)}.");
            _services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
        }

         IQueryHandler IQueryHandlerFactory.Create(Type handlerType)
        {
            if (_services == null)
                throw new InvalidOperationException($"A service provider must be assigned to the {nameof(AcHandlerFactory)} in advance of its use.");

             var scope = _services.CreateScope();
            var result = (IQueryHandler)scope.ServiceProvider.GetService(handlerType);
            _scopes.Add(result, scope);
            return result;
        }

         void IQueryHandlerFactory.Release(IQueryHandler handler)
        {
            if (_scopes.TryGetValue(handler, out var scope))
            {
                scope.Dispose();
                _scopes.Remove(handler);
            }
        }

         T IQueryHandlerDecoratorFactory.Create<T>(Type decoratorType)
        {
            return (T)_services.GetService(decoratorType);
        }

         void IQueryHandlerDecoratorFactory.Release<T>(T handler)
        {
            // no op
        }
    }

The decorator part is not scoped here, but it may be useful to do that too.

I think the handler factory here needs to be extended to provide for a wider array of use cases (or have a way to pick a factory based on a specific use case). Instantiating a new service provider on each query appears to be hard on memory, at least when coupled with EF Core.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
holytshirtcommented, Sep 8, 2019

@colinangusmackay I missed registering the default decorator for the fallback policy. I’ve fixed it now with release 2.0.78. I also tweaked the registration code and have cleaned it up a lot. Always easier to look back at code a short period after writing it.

1reaction
colinangusmackaycommented, Aug 30, 2019

As promised, an update

Checked and confirmed two out of three applications are working well with this fix. The last application is more complex and taking more time… and that’s the one that caused us most problems with Darker previously, so I’m treading more carefully with it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Troubleshoot out of memory or low memory issues in SQL ...
Provides troubleshooting steps to address out of memory or low memory issues in SQL Server.
Read more >
Troubleshoot slow performance or low memory issues ...
Provides troubleshooting steps to identify and reduce memory grant memory consumption in SQL Server.
Read more >
Preventing Out-of-memory errors in Lifecycle Query Engine
Problem. OutOfMemoryErrors are caused by a backlog of transactional journal writebacks from Lifecycle Query Engine. This usually happens when indexing activity ...
Read more >
How to Solve the Memory Warning in Running a Query ...
There are several common issues that can cause memory warnings in Power BI Desktop. Some of these include importing too much data, running ......
Read more >
Three Database Performance Problems That In-memory ...
You have lots of customer data to manipulate · Queries are too slow for immediate analysis · Online search is too slow ·...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found