Skip to content

Memory leak in compiled query cache (compiled Func<QueryContext,T>) #21016

@rducom

Description

@rducom

Path to the leak

We currently have a memory leak in production since we migrate from 2.1 to 3.1, exacerbated by the following context :

  • we are supporting a dynamic query language, so we receive a lot of different query shapes
  • we don't use DbContext Pooling, so our DbContext are not "cleaned" on dispose
  • we are on 3.1.4, and ServiceProviderEngineScope stopped removing ResolvedServices recently (since 3.0?)

We have double checked we never use Expression.Constant directly in our code base (we already have had this leak before)

The leak is related to DiagnosticsLogger<T> instances.
image

Reasons for the leak

To summarize : each compiled query holds a reference to a scoped ResolvedServices existing when the query have been .Compile(). This behavior leaks memory. The fact "Singleton" compiled queries (Func<QueryContext,T>) are holding Scoped instances is a design mistake at the root of the leak.

Resolution

While debugging, I noticed the only reason for DiagnosticsLogger to be Scoped is for the IInterceptors property introduced in #16252 . For all the code paths around cached queries compilation, there's no need to have references to IInterceptors, the only need is to Log.

A clean fix should be to provide only singleton to the Func<QueryContext,T> closure, without reference to IServiceProvider or any other scoped instance.

Suggested PR : #21017

Note : I've validated the fix on our code base : before the fix, we keep references to all our DbContext upon GC. After this PR, we no longer have references to any DbContext after GC

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions