Skip to content

Fix closure type instance caching problem (issue #224) #255

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

Merged
merged 12 commits into from
Jun 30, 2022
Merged
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
49 changes: 46 additions & 3 deletions Orm/Xtensive.Orm.Tests.Core/Reflection/TypeHelperTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (C) 2003-2010 Xtensive LLC.
// All rights reserved.
// For conditions of distribution and use, see license.
// Copyright (C) 2007-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: Alex Yakunin
// Created: 2007.12.17

Expand All @@ -14,6 +14,7 @@
using Xtensive.Core;
using Xtensive.Reflection;
using Xtensive.Orm.Tests;
using System.Linq;

namespace Xtensive.Orm.Tests.Core.Reflection
{
Expand Down Expand Up @@ -480,5 +481,47 @@ public void GenericIsNullableTest()
Assert.IsFalse(TypeHelper.IsNullable<int>());
Assert.IsFalse(TypeHelper.IsNullable<string>());
}

[Test]
public void IsValueTupleTest()
{
var tupleTypes = new[] {
typeof (ValueTuple<int>),
typeof (ValueTuple<int, int>),
typeof (ValueTuple<int, int, int>),
typeof (ValueTuple<int, int, int, int>),
typeof (ValueTuple<int, int, int, int, int>),
typeof (ValueTuple<int, int, int, int, int, int>),
typeof (ValueTuple<int, int, int, int, int, int, int>),
typeof (ValueTuple<int, int, int, int, int, int, int, int>)
};

var otherTypes = new[] {
typeof (string),
typeof (char),
typeof (bool),
typeof (DateTime),
typeof (TimeSpan),
typeof (Guid),
typeof (TypeCode),
typeof (byte[]),
typeof (Key),
this.GetType()
};

var startingToken = tupleTypes[0].MetadataToken;

Assert.That(
tupleTypes.Select(t => t.MetadataToken - startingToken).SequenceEqual(Enumerable.Range(0, tupleTypes.Length)),
Is.True);

foreach (var type in tupleTypes) {
Assert.IsTrue(type.IsValueTuple());
}

foreach (var type in otherTypes) {
Assert.IsFalse(type.IsValueTuple());
}
}
}
}
132 changes: 117 additions & 15 deletions Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Xtensive.Orm.Tests.Issues
namespace IssueGithub0224_DelayedQueryCapture_Model
{
[HierarchyRoot]
class Item : Entity
public class Item : Entity
{
[Field, Key]
public int Id { get; private set; }
Expand All @@ -32,50 +32,133 @@ class Item : Entity
[Serializable]
public class IssueGithub0224_DelayedQueryCapture : AutoBuildTest
{
public class OtherService

#region Services

public class OtherService1
{
public static volatile int InstanceCount;

public int N;

public OtherService1()
{
_ = Interlocked.Increment(ref InstanceCount);
}

~OtherService1()
{
_ = Interlocked.Decrement(ref InstanceCount);
}
}

public class OtherService2
{
public static volatile int InstanceCount;

public int N;

public OtherService2()
{
_ = Interlocked.Increment(ref InstanceCount);
}

~OtherService2()
{
_ = Interlocked.Decrement(ref InstanceCount);
}
}

public class OtherService3
{
public static volatile int InstanceCount;

public int N;

public OtherService()
public OtherService3()
{
Interlocked.Increment(ref InstanceCount);
_ = Interlocked.Increment(ref InstanceCount);
}

~OtherService()
~OtherService3()
{
Interlocked.Decrement(ref InstanceCount);
_ = Interlocked.Decrement(ref InstanceCount);
}
}

#endregion

protected override DomainConfiguration BuildConfiguration()
{
var config = base.BuildConfiguration();
config.Types.Register(typeof(Item).Assembly, typeof(Item).Namespace);
config.Types.Register(typeof(Item));
return config;
}

[Test]
public void DelayedQueryCapture()
public void DelayedQueryWithIncludeTest()
{
using (var session = Domain.OpenSession())
using (var t = session.OpenTransaction()) {
var item = new Item() { Tag = 10 };
DelayedQueryWithInclude(session);
t.Complete();
}
TestHelper.CollectGarbage(true);
Assert.AreEqual(0, OtherService1.InstanceCount);
}

[Test]
public void DelayedQueryWithContainsTest()
{
using (var session = Domain.OpenSession())
using (var t = session.OpenTransaction()) {
var item = new Item() { Tag = 10 };
DelayedQueryWithContains(session);
t.Complete();
}

TestHelper.CollectGarbage(true);
Assert.AreEqual(0, OtherService2.InstanceCount);
}

[Test]
public void DelayedQueryWithEqualityTest()
{
using (var session = Domain.OpenSession())
using (var t = session.OpenTransaction()) {
var item = new Item() { Tag = 10 };
DelayedQuery(session);
DelayedQueryWithEquality(session);
t.Complete();
}
GC.Collect();
Thread.Sleep(1000);
GC.Collect();
Assert.AreEqual(0, OtherService.InstanceCount);

TestHelper.CollectGarbage(true);
Assert.AreEqual(0, OtherService3.InstanceCount);
}

private void DelayedQueryWithEquality(Session session)
{
var id = 1;
var otherService = new OtherService3();

var items = session.Query.CreateDelayedQuery("ABC", q =>
from t in q.All<Item>()
where t.Id == id
select t).ToArray();

var bb1 = items
.Select(a => new {
a.Id,
A = new {
B = otherService.N == a.Id
},
});
}

private void DelayedQuery(Session session)
private void DelayedQueryWithInclude(Session session)
{
var ids = new[] { 1, 2 };
var otherService = new OtherService();
var otherService = new OtherService1();

var items = session.Query.CreateDelayedQuery(q =>
from t in q.All<Item>()
Expand All @@ -90,5 +173,24 @@ where t.Id.In(ids)
},
});
}

private void DelayedQueryWithContains(Session session)
{
var ids = new[] { 1, 2 };
var otherService = new OtherService2();

var items = session.Query.CreateDelayedQuery(q =>
from t in q.All<Item>()
where ids.Contains(t.Id)
select t).ToArray();

var bb1 = items
.Select(a => new {
a.Id,
A = new {
B = otherService.N == a.Id
},
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2009-2020 Xtensive LLC.
// Copyright (C) 2009-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: Alexis Kochetov
Expand Down
45 changes: 12 additions & 33 deletions Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created: 2012.01.27

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand All @@ -20,8 +21,6 @@ namespace Xtensive.Orm.Internals
{
internal class CompiledQueryRunner
{
private static readonly Func<FieldInfo, bool> FieldIsSimple = fieldInfo => IsSimpleType(fieldInfo.FieldType);

private readonly Domain domain;
private readonly Session session;
private readonly QueryEndpoint endpoint;
Expand Down Expand Up @@ -111,24 +110,24 @@ private DelayedQuery<TElement> CreateDelayedSequenceQuery<TElement>(
private ParameterizedQuery GetScalarQuery<TResult>(
Func<QueryEndpoint, TResult> query, bool executeAsSideEffect, out TResult result)
{
var cacheable = AllocateParameterAndReplacer();
AllocateParameterAndReplacer();

var parameterContext = new ParameterContext(outerContext);
parameterContext.SetValue(queryParameter, queryTarget);
var scope = new CompiledQueryProcessingScope(
queryParameter, queryParameterReplacer, parameterContext, executeAsSideEffect);

using (scope.Enter()) {
result = query.Invoke(endpoint);
}

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

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

return parameterizedQuery;
}

Expand All @@ -140,26 +139,25 @@ private ParameterizedQuery GetSequenceQuery<TElement>(
return parameterizedQuery;
}

var cacheable = AllocateParameterAndReplacer();
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;
}

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

return parameterizedQuery;
}

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

var closureType = queryTarget.GetType();
Expand Down Expand Up @@ -199,31 +197,12 @@ private bool AllocateParameterAndReplacer()
}
return null;
});

return !TypeHelper.IsClosure(closureType)
|| closureType.GetFields().All(FieldIsSimple);
}

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

private ParameterizedQuery GetCachedQuery() =>
domain.QueryCache.TryGetItem(queryKey, true, out var item) ? item.Second : null;

private void PutCachedQuery(ParameterizedQuery parameterizedQuery) =>
private void PutQueryToCache(ParameterizedQuery parameterizedQuery) =>
domain.QueryCache.Add(new Pair<object, ParameterizedQuery>(queryKey, parameterizedQuery));

private ParameterContext CreateParameterContext(ParameterizedQuery query)
Expand Down
Loading