Skip to content

Analyze Target object field types to avoid caching refs to long-living objects #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Xtensive.Orm.Internals
{
internal sealed class CompiledQueryProcessingScope
{
private TranslatedQuery parameterizedQuery;
private ParameterizedQuery parameterizedQuery;

public Parameter QueryParameter { get; }
public ExtendedExpressionReplacer QueryParameterReplacer { get; }
Expand Down Expand Up @@ -43,7 +43,7 @@ public ThreadBindingToken(CompiledQueryProcessingScope scope)
public ThreadBindingToken Enter() => new ThreadBindingToken(this);

/// <exception cref="NotSupportedException">Second attempt to set this property.</exception>
public TranslatedQuery ParameterizedQuery {
public ParameterizedQuery ParameterizedQuery {
get => parameterizedQuery;
set {
if (parameterizedQuery != null) {
Expand Down
97 changes: 55 additions & 42 deletions Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2012-2021 Xtensive LLC.
// Copyright (C) 2012-2022 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
Expand All @@ -7,6 +7,7 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Xtensive.Caching;
Expand All @@ -19,6 +20,8 @@ namespace Xtensive.Orm.Internals
{
internal class CompiledQueryRunner
{
private static readonly Func<FieldInfo, bool> FieldIsSimple = fieldInfo => TypeIsSimple(fieldInfo.FieldType);

private readonly Domain domain;
private readonly Session session;
private readonly QueryEndpoint endpoint;
Expand All @@ -44,7 +47,7 @@ public QueryResult<TElement> ExecuteCompiled<TElement>(Func<QueryEndpoint, IOrde
public TResult ExecuteCompiled<TResult>(Func<QueryEndpoint, TResult> query)
{
var parameterizedQuery = GetCachedQuery();
if (parameterizedQuery!=null) {
if (parameterizedQuery != null) {
return parameterizedQuery.ExecuteScalar<TResult>(session, CreateParameterContext(parameterizedQuery));
}

Expand All @@ -65,12 +68,12 @@ public Task<QueryResult<TElement>> ExecuteCompiledAsync<TElement>(

public Task<QueryResult<TElement>> ExecuteCompiledAsync<TElement>(
Func<QueryEndpoint, IOrderedQueryable<TElement>> query, CancellationToken token) =>
ExecuteCompiledAsync((Func<QueryEndpoint, IQueryable<TElement>>)query, token);
ExecuteCompiledAsync((Func<QueryEndpoint, IQueryable<TElement>>) query, token);

public Task<TResult> ExecuteCompiledAsync<TResult>(Func<QueryEndpoint, TResult> query, CancellationToken token)
{
var parameterizedQuery = GetCachedQuery();
if (parameterizedQuery!=null) {
if (parameterizedQuery != null) {
token.ThrowIfCancellationRequested();
return parameterizedQuery.ExecuteScalarAsync<TResult>(session, CreateParameterContext(parameterizedQuery), token);
}
Expand Down Expand Up @@ -108,7 +111,7 @@ private DelayedQuery<TElement> CreateDelayedSequenceQuery<TElement>(
private ParameterizedQuery GetScalarQuery<TResult>(
Func<QueryEndpoint, TResult> query, bool executeAsSideEffect, out TResult result)
{
AllocateParameterAndReplacer();
var cacheable = AllocateParameterAndReplacer();

var parameterContext = new ParameterContext(outerContext);
parameterContext.SetValue(queryParameter, queryTarget);
Expand All @@ -118,80 +121,90 @@ private ParameterizedQuery GetScalarQuery<TResult>(
result = query.Invoke(endpoint);
}

var parameterizedQuery = (ParameterizedQuery) scope.ParameterizedQuery;
if (parameterizedQuery==null && queryTarget!=null) {
var parameterizedQuery = scope.ParameterizedQuery;
if (parameterizedQuery == null && queryTarget != null) {
throw new NotSupportedException(Strings.ExNonLinqCallsAreNotSupportedWithinQueryExecuteDelayed);
}

PutCachedQuery(parameterizedQuery);
if (cacheable) {
PutCachedQuery(parameterizedQuery);
}
return parameterizedQuery;
}

private ParameterizedQuery GetSequenceQuery<TElement>(
Func<QueryEndpoint, IQueryable<TElement>> query)
{
var parameterizedQuery = GetCachedQuery();
if (parameterizedQuery!=null) {
if (parameterizedQuery != null) {
return parameterizedQuery;
}

AllocateParameterAndReplacer();
var cacheable = AllocateParameterAndReplacer();
var scope = new CompiledQueryProcessingScope(queryParameter, queryParameterReplacer);
using (scope.Enter()) {
var result = query.Invoke(endpoint);
var translatedQuery = endpoint.Provider.Translate(result.Expression);
parameterizedQuery = (ParameterizedQuery) translatedQuery;
}

PutCachedQuery(parameterizedQuery);
if (cacheable) {
PutCachedQuery(parameterizedQuery);
}
return parameterizedQuery;
}

private void AllocateParameterAndReplacer()
// Returns true is query is cacheable (target contains only simple-type captured vars).
private bool AllocateParameterAndReplacer()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@commit-n-run Could you review this approach?

{
if (queryTarget == null) {
queryParameter = null;
queryParameterReplacer = new ExtendedExpressionReplacer(e => e);
return;
return true;
}

var closureType = queryTarget.GetType();
var parameterType = WellKnownOrmTypes.ParameterOfT.CachedMakeGenericType(closureType);
var valueMemberInfo = parameterType.GetProperty(nameof(Parameter<object>.Value), closureType);
queryParameter = (Parameter) System.Activator.CreateInstance(parameterType, "pClosure");
queryParameterReplacer = new ExtendedExpressionReplacer(expression => {
if (expression.NodeType == ExpressionType.Constant) {
if ((expression as ConstantExpression).Value == null) {
if (expression.NodeType != ExpressionType.Constant || (expression as ConstantExpression).Value == null) {
return null;
}
if (expression.Type.IsClosure()) {
if (expression.Type==closureType) {
return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo);
}
else {
throw new NotSupportedException(string.Format(
Strings.ExExpressionDefinedOutsideOfCachingQueryClosure, expression));
}
}

if (closureType.DeclaringType==null) {
if (expression.Type.IsAssignableFrom(closureType))
return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo);
}
else {
if (expression.Type.IsAssignableFrom(closureType))
return Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo);
if (expression.Type.IsAssignableFrom(closureType.DeclaringType)) {
var memberInfo = closureType.TryGetFieldInfoFromClosure(expression.Type);
if (memberInfo != null)
return Expression.MakeMemberAccess(
Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo),
memberInfo);
}
}

var expressionType = expression.Type;
if (expressionType.IsClosure()) {
return expressionType == closureType
? Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo)
: throw new NotSupportedException(string.Format(Strings.ExExpressionDefinedOutsideOfCachingQueryClosure, expression));
}
return null;

return expressionType.IsAssignableFrom(closureType)
? Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo)
: closureType.DeclaringType != null
&& expressionType.IsAssignableFrom(closureType.DeclaringType)
&& closureType.TryGetFieldInfoFromClosure(expressionType) is MemberInfo memberInfo
? Expression.MakeMemberAccess(Expression.MakeMemberAccess(Expression.Constant(queryParameter, parameterType), valueMemberInfo), memberInfo)
: null;
});
return !closureType.Name.Contains("<>c__DisplayClass") // 'DisplayClass' is generated class for captured objects
|| closureType.GetFields().All(FieldIsSimple);
}

private static bool TypeIsSimple(Type type)
{
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsGenericType) {
var genericDef = typeInfo.GetGenericTypeDefinition();
return (genericDef == WellKnownTypes.NullableOfT || genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyList))
&& TypeIsSimple(typeInfo.GetGenericArguments()[0]);
}
else if (typeInfo.IsArray) {
return TypeIsSimple(typeInfo.GetElementType());
}
else {
return typeInfo.IsPrimitive || typeInfo.IsEnum || type == WellKnownTypes.String || type == WellKnownTypes.Decimal;
}
}

private ParameterizedQuery GetCachedQuery() =>
Expand All @@ -203,7 +216,7 @@ private void PutCachedQuery(ParameterizedQuery parameterizedQuery) =>
private ParameterContext CreateParameterContext(ParameterizedQuery query)
{
var parameterContext = new ParameterContext(outerContext);
if (query.QueryParameter!=null) {
if (query.QueryParameter != null) {
parameterContext.SetValue(query.QueryParameter, queryTarget);
}

Expand Down
2 changes: 2 additions & 0 deletions Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the License.txt file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -73,6 +74,7 @@ internal static class WellKnownTypes

public static readonly Type ByteArray = typeof(byte[]);
public static readonly Type ObjectArray = typeof(object[]);
public static readonly Type IReadOnlyList = typeof(IReadOnlyList<>);

public static readonly Type DefaultMemberAttribute = typeof(DefaultMemberAttribute);
}
Expand Down