From 724d1be204bad606b6dac70e4d4046526042cf02 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Mon, 7 Mar 2022 20:46:38 -0800 Subject: [PATCH 1/7] Cached query captures object in closure --- .../Issues/Issue_DelayedQueryCapture.cs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100755 Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs diff --git a/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs b/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs new file mode 100755 index 0000000000..fc5c638c35 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs @@ -0,0 +1,94 @@ +// Copyright (C) 2022 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections; +using System.Diagnostics; +using System.Linq; +using System.Linq.Dynamic; +using System.Reflection; +using System.Threading; +using NUnit.Framework; +using Xtensive.Caching; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Tests.Issues.Issue0628_ExecuteFutureScalarError_Model; + +namespace Xtensive.Orm.Tests.Issues +{ + namespace Issue_DelayedQueryCapture_Model + { + [HierarchyRoot] + class Item : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public int Tag { get; set; } + } + } + + [Serializable] + public class Issue_DelayedQueryCapture : AutoBuildTest + { + public class OtherService + { + public static volatile int InstanceCount; + + public int N; + + public OtherService() + { + Interlocked.Increment(ref InstanceCount); + } + + ~OtherService() + { + Interlocked.Decrement(ref InstanceCount); + } + } + + + protected override DomainConfiguration BuildConfiguration() + { + var config = base.BuildConfiguration(); + config.Types.Register(typeof(Item).Assembly, typeof(Item).Namespace); + return config; + } + + [Test] + public void DelayedQueryCapture() + { + using (var session = Domain.OpenSession()) + using (var t = session.OpenTransaction()) { + var item = new Item() { Tag = 10 }; + DelayedQuery(session); + t.Complete(); + } + GC.Collect(); + Thread.Sleep(1000); + GC.Collect(); + Assert.AreEqual(0, OtherService.InstanceCount); + } + + private void DelayedQuery(Session session) + { + var ids = new[] { 1, 2 }; + var otherService = new OtherService(); + + var items = session.Query.CreateDelayedQuery(q => + from t in q.All() + where t.Id.In(ids) + select t).ToArray(); + + var bb1 = items + .Select(a => new { + a.Id, + A = new { + B = otherService.N == a.Id + }, + }); + } + } +} From c2ad74b8761e539c0a0407d96bcde568e7b3b646 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Mon, 7 Mar 2022 21:01:31 -0800 Subject: [PATCH 2/7] Fix namespaces --- Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs b/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs index fc5c638c35..f3c4b05ad0 100755 --- a/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs +++ b/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs @@ -12,7 +12,7 @@ using NUnit.Framework; using Xtensive.Caching; using Xtensive.Orm.Configuration; -using Xtensive.Orm.Tests.Issues.Issue0628_ExecuteFutureScalarError_Model; +using Xtensive.Orm.Tests.Issues.Issue_DelayedQueryCapture_Model; namespace Xtensive.Orm.Tests.Issues { From 49f59b0fd0f9521904347be1d6c90b97d7aa402a Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Tue, 8 Mar 2022 23:39:45 -0800 Subject: [PATCH 3/7] Rename namespace & class --- ...eryCapture.cs => IssueGithub0224_DelayedQueryCapture.cs} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename Orm/Xtensive.Orm.Tests/Issues/{Issue_DelayedQueryCapture.cs => IssueGithub0224_DelayedQueryCapture.cs} (91%) diff --git a/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs similarity index 91% rename from Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs rename to Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs index f3c4b05ad0..47a71904d9 100755 --- a/Orm/Xtensive.Orm.Tests/Issues/Issue_DelayedQueryCapture.cs +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueGithub0224_DelayedQueryCapture.cs @@ -12,11 +12,11 @@ using NUnit.Framework; using Xtensive.Caching; using Xtensive.Orm.Configuration; -using Xtensive.Orm.Tests.Issues.Issue_DelayedQueryCapture_Model; +using Xtensive.Orm.Tests.Issues.IssueGithub0224_DelayedQueryCapture_Model; namespace Xtensive.Orm.Tests.Issues { - namespace Issue_DelayedQueryCapture_Model + namespace IssueGithub0224_DelayedQueryCapture_Model { [HierarchyRoot] class Item : Entity @@ -30,7 +30,7 @@ class Item : Entity } [Serializable] - public class Issue_DelayedQueryCapture : AutoBuildTest + public class IssueGithub0224_DelayedQueryCapture : AutoBuildTest { public class OtherService { From 09d03521e53fff4411244f54b3349d459c25d968 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Mon, 7 Mar 2022 21:41:01 -0800 Subject: [PATCH 4/7] Fix Issue 224 --- .../Orm/Internals/CompiledQueryRunner.cs | 40 +++++++++++++++---- Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs | 4 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index bc036269a3..648c513d9c 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -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 @@ -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; @@ -19,6 +20,8 @@ namespace Xtensive.Orm.Internals { internal class CompiledQueryRunner { + private static readonly Func FieldIsSimple = fieldInfo => TypeIsSimple(fieldInfo.FieldType); + private readonly Domain domain; private readonly Session session; private readonly QueryEndpoint endpoint; @@ -108,7 +111,7 @@ private DelayedQuery CreateDelayedSequenceQuery( private ParameterizedQuery GetScalarQuery( Func query, bool executeAsSideEffect, out TResult result) { - AllocateParameterAndReplacer(); + var cacheable = AllocateParameterAndReplacer(); var parameterContext = new ParameterContext(outerContext); parameterContext.SetValue(queryParameter, queryTarget); @@ -123,7 +126,9 @@ private ParameterizedQuery GetScalarQuery( throw new NotSupportedException(Strings.ExNonLinqCallsAreNotSupportedWithinQueryExecuteDelayed); } - PutCachedQuery(parameterizedQuery); + if (cacheable) { + PutCachedQuery(parameterizedQuery); + } return parameterizedQuery; } @@ -135,7 +140,7 @@ private ParameterizedQuery GetSequenceQuery( return parameterizedQuery; } - AllocateParameterAndReplacer(); + var cacheable = AllocateParameterAndReplacer(); var scope = new CompiledQueryProcessingScope(queryParameter, queryParameterReplacer); using (scope.Enter()) { var result = query.Invoke(endpoint); @@ -143,16 +148,18 @@ private ParameterizedQuery GetSequenceQuery( parameterizedQuery = (ParameterizedQuery) translatedQuery; } - PutCachedQuery(parameterizedQuery); + if (cacheable) { + PutCachedQuery(parameterizedQuery); + } return parameterizedQuery; } - private void AllocateParameterAndReplacer() + private bool AllocateParameterAndReplacer() { if (queryTarget == null) { queryParameter = null; queryParameterReplacer = new ExtendedExpressionReplacer(e => e); - return; + return true; } var closureType = queryTarget.GetType(); @@ -192,6 +199,25 @@ private void AllocateParameterAndReplacer() } return 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() => diff --git a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs index d8378b5a7d..d8dcdecb9a 100644 --- a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs @@ -1,8 +1,9 @@ -// Copyright (C) 2020 Xtensive LLC. +// Copyright (C) 2020-2022 Xtensive LLC. // This code is distributed under MIT license terms. // 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; @@ -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); } From 645987dd06125a5b3a783e91020717b21923e298 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Tue, 15 Mar 2022 14:34:55 -0700 Subject: [PATCH 5/7] Rename WellKnownTypes.IReadOnlyList -> .IReadOnlyListOfT --- Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs | 2 +- Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index 648c513d9c..c7008251a9 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -209,7 +209,7 @@ 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)) + return (genericDef == WellKnownTypes.NullableOfT || genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyListOfT)) && TypeIsSimple(typeInfo.GetGenericArguments()[0]); } else if (typeInfo.IsArray) { diff --git a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs index d8dcdecb9a..6c4c876b67 100644 --- a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs @@ -74,7 +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 IReadOnlyListOfT = typeof(IReadOnlyList<>); public static readonly Type DefaultMemberAttribute = typeof(DefaultMemberAttribute); } From 51f7203b596f7a5c3c721dcb762196aa399b807c Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Tue, 15 Mar 2022 14:38:38 -0700 Subject: [PATCH 6/7] Rename IsSimpleType --- Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index c7008251a9..51a627c12d 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -20,7 +20,7 @@ namespace Xtensive.Orm.Internals { internal class CompiledQueryRunner { - private static readonly Func FieldIsSimple = fieldInfo => TypeIsSimple(fieldInfo.FieldType); + private static readonly Func FieldIsSimple = fieldInfo => IsSimpleType(fieldInfo.FieldType); private readonly Domain domain; private readonly Session session; @@ -204,16 +204,16 @@ private bool AllocateParameterAndReplacer() || closureType.GetFields().All(FieldIsSimple); } - private static bool TypeIsSimple(Type type) + 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)) - && TypeIsSimple(typeInfo.GetGenericArguments()[0]); + && IsSimpleType(typeInfo.GetGenericArguments()[0]); } else if (typeInfo.IsArray) { - return TypeIsSimple(typeInfo.GetElementType()); + return IsSimpleType(typeInfo.GetElementType()); } else { return typeInfo.IsPrimitive || typeInfo.IsEnum || type == WellKnownTypes.String || type == WellKnownTypes.Decimal; From ba7648ea5d7552dd8abaa6dd53d4428911fb2fa5 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Tue, 15 Mar 2022 14:46:58 -0700 Subject: [PATCH 7/7] Use TypeHelper.IsClosure() in AllocateParameterAndReplacer() --- Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs index 51a627c12d..e499166a64 100644 --- a/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs +++ b/Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs @@ -200,7 +200,7 @@ private bool AllocateParameterAndReplacer() return null; }); - return !closureType.Name.Contains("<>c__DisplayClass") // 'DisplayClass' is generated class for captured objects + return !TypeHelper.IsClosure(closureType) || closureType.GetFields().All(FieldIsSimple); }