Skip to content

Commit 50e3dbd

Browse files
yinzararoji
authored andcommitted
Keep parameter values out IMemoryCache in RelationalCommandCache
Store only nullness and array lengths in struct form to prevent parameters memory leaks Fixes dotnet#34028 Co-authored-by: Shay Rojansky <[email protected]> (cherry picked from commit af420cd)
1 parent ae85c4b commit 50e3dbd

File tree

1 file changed

+29
-21
lines changed

1 file changed

+29
-21
lines changed

src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections;
54
using System.Collections.Concurrent;
65
using System.Runtime.CompilerServices;
76
using Microsoft.Extensions.Caching.Memory;
@@ -106,11 +105,25 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
106105
}
107106
}
108107

109-
private readonly struct CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
110-
: IEquatable<CommandCacheKey>
108+
private readonly struct CommandCacheKey : IEquatable<CommandCacheKey>
111109
{
112-
private readonly Expression _queryExpression = queryExpression;
113-
private readonly IReadOnlyDictionary<string, object?> _parameterValues = parameterValues;
110+
private readonly Expression _queryExpression;
111+
private readonly Dictionary<string, ParameterInfo> _parameterInfos;
112+
113+
internal CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
114+
{
115+
_queryExpression = queryExpression;
116+
_parameterInfos = new Dictionary<string, ParameterInfo>();
117+
118+
foreach (var (key, value) in parameterValues)
119+
{
120+
_parameterInfos[key] = new ParameterInfo
121+
{
122+
IsNull = value is null,
123+
ObjectArrayLength = value is object[] arr ? arr.Length : null
124+
};
125+
}
126+
}
114127

115128
public override bool Equals(object? obj)
116129
=> obj is CommandCacheKey commandCacheKey
@@ -124,27 +137,18 @@ public bool Equals(CommandCacheKey commandCacheKey)
124137
return false;
125138
}
126139

127-
if (_parameterValues.Count > 0)
140+
Check.DebugAssert(
141+
_parameterInfos.Count == commandCacheKey._parameterInfos.Count,
142+
"Parameter Count mismatch between identical queries");
143+
144+
if (_parameterInfos.Count > 0)
128145
{
129-
foreach (var (key, value) in _parameterValues)
146+
foreach (var (key, info) in _parameterInfos)
130147
{
131-
if (!commandCacheKey._parameterValues.TryGetValue(key, out var otherValue))
148+
if (!commandCacheKey._parameterInfos.TryGetValue(key, out var otherInfo) || info != otherInfo)
132149
{
133150
return false;
134151
}
135-
136-
// ReSharper disable once ArrangeRedundantParentheses
137-
if ((value == null) != (otherValue == null))
138-
{
139-
return false;
140-
}
141-
142-
if (value is IEnumerable
143-
&& value.GetType() == typeof(object[]))
144-
{
145-
// FromSql parameters must have the same number of elements
146-
return ((object[])value).Length == (otherValue as object[])?.Length;
147-
}
148152
}
149153
}
150154

@@ -154,4 +158,8 @@ public bool Equals(CommandCacheKey commandCacheKey)
154158
public override int GetHashCode()
155159
=> RuntimeHelpers.GetHashCode(_queryExpression);
156160
}
161+
162+
// Note that we keep only the null-ness of parameters (and array length for FromSql object arrays),
163+
// and avoid referencing the actual parameter data (see #34028).
164+
private readonly record struct ParameterInfo(bool IsNull, int? ObjectArrayLength);
157165
}

0 commit comments

Comments
 (0)