From 391d23157ca9536d40f60db5cbbbe230d4c2b52a Mon Sep 17 00:00:00 2001 From: Gunnar Liljas Date: Sat, 2 Dec 2017 01:20:32 +0100 Subject: [PATCH 01/13] Batching and distincting cache invalidation calls --- .../SecondLevelCacheTest/InvalidationTests.cs | 112 ++++++++++++++++++ .../SecondLevelCacheTest/InvalidationTests.cs | 91 ++++++++++++++ src/NHibernate.Test/TestExtensions.cs | 32 +++++ .../Async/Cache/UpdateTimestampsCache.cs | 8 +- src/NHibernate/Async/Engine/ActionQueue.cs | 74 +++++++----- .../Async/Engine/Query/NativeSQLQueryPlan.cs | 2 +- .../ANTLR/Exec/AbstractStatementExecutor.cs | 4 +- src/NHibernate/Cache/UpdateTimestampsCache.cs | 10 +- src/NHibernate/Engine/ActionQueue.cs | 71 +++++++++-- 9 files changed, 349 insertions(+), 55 deletions(-) create mode 100644 src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs create mode 100644 src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs create mode 100644 src/NHibernate.Test/TestExtensions.cs diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs new file mode 100644 index 00000000000..41103e97e5e --- /dev/null +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -0,0 +1,112 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Cfg; +using NHibernate.Impl; +using NHibernate.Test.SecondLevelCacheTests; +using NSubstitute; +using NUnit.Framework; +using Environment = System.Environment; + +namespace NHibernate.Test.SecondLevelCacheTest +{ + using System.Threading; + [TestFixture] + public class InvalidationTestsAsync : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] { "SecondLevelCacheTest.Item.hbm.xml" }; + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + configuration.Properties[Cfg.Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; + configuration.Properties[Cfg.Environment.UseQueryCache] = "true"; + } + + [Test] + public async Task InvalidatesEntitiesAsync() + { + var cache = Substitute.For(Sfi.Settings, new Dictionary()); + ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); + + var items = new List(); + using (ISession session = OpenSession()) + { + using (ITransaction tx = session.BeginTransaction()) + { + foreach (var i in Enumerable.Range(1, 10)) + { + var item = new Item { Id = i }; + await (session.SaveAsync(item)); + } + await (tx.CommitAsync()); + } + + using (ITransaction tx = session.BeginTransaction()) + { + foreach (var i in Enumerable.Range(1, 10)) + { + var item = await (session.GetAsync(i)); + item.Name = item.Id.ToString(); + } + await (tx.CommitAsync()); + } + + using (ITransaction tx = session.BeginTransaction()) + { + foreach (var i in Enumerable.Range(1, 10)) + { + var item = await (session.GetAsync(i)); + await (session.DeleteAsync(item)); + } + await (tx.CommitAsync()); + } + } + //Should receive one preinvalidation and one invalidation per commit + await (cache.Received(3).PreInvalidateAsync(Arg.Is(x => x.Length==1 && (string)x[0] == "Item"), CancellationToken.None)); + await (cache.Received(3).InvalidateAsync(Arg.Is(x => x.Length == 1 && (string) x[0] == "Item"), CancellationToken.None)); + + } + + public async Task CleanUpAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + await (s.DeleteAsync("from Item", cancellationToken)); + await (tx.CommitAsync(cancellationToken)); + } + } + + public void CleanUp() + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + s.Delete("from Item"); + tx.Commit(); + } + } + + protected override void OnTearDown() + { + CleanUp(); + base.OnTearDown(); + } + } +} diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs new file mode 100644 index 00000000000..ce4bdb67aa7 --- /dev/null +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Cfg; +using NHibernate.Impl; +using NHibernate.Test.SecondLevelCacheTests; +using NSubstitute; +using NUnit.Framework; +using Environment = System.Environment; + +namespace NHibernate.Test.SecondLevelCacheTest +{ + [TestFixture] + public class InvalidationTests : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] { "SecondLevelCacheTest.Item.hbm.xml" }; + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + configuration.Properties[Cfg.Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; + configuration.Properties[Cfg.Environment.UseQueryCache] = "true"; + } + + [Test] + public void InvalidatesEntities() + { + var cache = Substitute.For(Sfi.Settings, new Dictionary()); + ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); + + var items = new List(); + using (ISession session = OpenSession()) + { + using (ITransaction tx = session.BeginTransaction()) + { + foreach (var i in Enumerable.Range(1, 10)) + { + var item = new Item { Id = i }; + session.Save(item); + } + tx.Commit(); + } + + using (ITransaction tx = session.BeginTransaction()) + { + foreach (var i in Enumerable.Range(1, 10)) + { + var item = session.Get(i); + item.Name = item.Id.ToString(); + } + tx.Commit(); + } + + using (ITransaction tx = session.BeginTransaction()) + { + foreach (var i in Enumerable.Range(1, 10)) + { + var item = session.Get(i); + session.Delete(item); + } + tx.Commit(); + } + } + //Should receive one preinvalidation and one invalidation per commit + cache.Received(3).PreInvalidate(Arg.Is(x => x.Length==1 && (string)x[0] == "Item")); + cache.Received(3).Invalidate(Arg.Is(x => x.Length == 1 && (string) x[0] == "Item")); + + } + + public void CleanUp() + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + s.Delete("from Item"); + tx.Commit(); + } + } + + protected override void OnTearDown() + { + CleanUp(); + base.OnTearDown(); + } + } +} diff --git a/src/NHibernate.Test/TestExtensions.cs b/src/NHibernate.Test/TestExtensions.cs new file mode 100644 index 00000000000..14dc253c464 --- /dev/null +++ b/src/NHibernate.Test/TestExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace NHibernate.Test +{ + //Utility extension methods for use in unit tests. + public static class TestExtensions + { + public static T SetPropertyUsingReflection(this T instance, Expression> property, TProp value) + { + var method = property.Body as MemberExpression; + var propertyInfo = typeof(T).GetProperty(method.Member.Name); + if (propertyInfo!= null && propertyInfo.CanWrite) + { + propertyInfo.SetValue(instance, value); + } + else + { + //camel cased field + var name = method.Member.Name.Substring(0, 1).ToLowerInvariant() + method.Member.Name.Substring(1); + var field = typeof(T).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); + if (field != null) + { + field.SetValue(instance, value); + } + } + return instance; + } + + } +} diff --git a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs index 86a89e211ae..ffbf291be76 100644 --- a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs @@ -34,7 +34,7 @@ public Task ClearAsync(CancellationToken cancellationToken) } [MethodImpl()] - public async Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) + public virtual async Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _preInvalidate.LockAsync()) @@ -52,7 +52,7 @@ public async Task PreInvalidateAsync(object[] spaces, CancellationToken cancella /// [MethodImpl()] - public async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken) + public virtual async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _invalidate.LockAsync()) @@ -69,7 +69,7 @@ public async Task InvalidateAsync(object[] spaces, CancellationToken cancellatio } [MethodImpl()] - public async Task IsUpToDateAsync(ISet spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken) + public virtual async Task IsUpToDateAsync(ISet spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _isUpToDate.LockAsync()) @@ -108,4 +108,4 @@ public async Task IsUpToDateAsync(ISet spaces, long timestamp /* H } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Async/Engine/ActionQueue.cs b/src/NHibernate/Async/Engine/ActionQueue.cs index 40bab6dfba4..69eae6ab42e 100644 --- a/src/NHibernate/Async/Engine/ActionQueue.cs +++ b/src/NHibernate/Async/Engine/ActionQueue.cs @@ -13,26 +13,16 @@ using System.Collections.Generic; using System.Linq; using System.Text; - +using System.Threading; +using System.Threading.Tasks; using NHibernate.Action; using NHibernate.Cache; using NHibernate.Type; namespace NHibernate.Engine { - using System.Threading.Tasks; - using System.Threading; public partial class ActionQueue { - - public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return RegisterCleanupActionsAsync(cleanupAction, cancellationToken); - } private async Task ExecuteActionsAsync(IList list, CancellationToken cancellationToken) { @@ -41,11 +31,24 @@ private async Task ExecuteActionsAsync(IList list, CancellationToken cancellatio // It will then fail here due to list being modified. (Some previous code was dodging the // trouble with a for loop which was not failing provided the list was not getting smaller. // But then it was clearing it without having executed added actions (if any), ...) + foreach (IExecutable executable in list) - await (ExecuteAsync(executable, cancellationToken)).ConfigureAwait(false); - + { + await (InnerExecuteAsync(executable, cancellationToken)).ConfigureAwait(false); + } list.Clear(); await (session.Batcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false); + + } + + private async Task AfterExecutionsAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (session.Factory.Settings.IsQueryCacheEnabled) + { + await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(executedSpaces.ToArray(), cancellationToken)).ConfigureAwait(false); + } + executedSpaces.Clear(); } public async Task ExecuteAsync(IExecutable executable, CancellationToken cancellationToken) @@ -53,25 +56,29 @@ public async Task ExecuteAsync(IExecutable executable, CancellationToken cancell cancellationToken.ThrowIfCancellationRequested(); try { - await (executable.ExecuteAsync(cancellationToken)).ConfigureAwait(false); + await (InnerExecuteAsync(executable, cancellationToken)).ConfigureAwait(false); } finally { - await (RegisterCleanupActionsAsync(executable, cancellationToken)).ConfigureAwait(false); + await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false); } } - - private async Task RegisterCleanupActionsAsync(IExecutable executable, CancellationToken cancellationToken) + + private async Task InnerExecuteAsync(IExecutable executable, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - beforeTransactionProcesses.Register(executable.BeforeTransactionCompletionProcess); - if (session.Factory.Settings.IsQueryCacheEnabled) + if (executable.PropertySpaces != null) { - string[] spaces = executable.PropertySpaces; - afterTransactionProcesses.AddSpacesToInvalidate(spaces); - await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(spaces, cancellationToken)).ConfigureAwait(false); + executedSpaces.UnionWith(executable.PropertySpaces); + } + try + { + await (executable.ExecuteAsync(cancellationToken)).ConfigureAwait(false); + } + finally + { + RegisterCleanupActions(executable); } - afterTransactionProcesses.Register(executable.AfterTransactionCompletionProcess); } /// @@ -94,12 +101,19 @@ public Task ExecuteInsertsAsync(CancellationToken cancellationToken) public async Task ExecuteActionsAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - await (ExecuteActionsAsync(insertions, cancellationToken)).ConfigureAwait(false); - await (ExecuteActionsAsync(updates, cancellationToken)).ConfigureAwait(false); - await (ExecuteActionsAsync(collectionRemovals, cancellationToken)).ConfigureAwait(false); - await (ExecuteActionsAsync(collectionUpdates, cancellationToken)).ConfigureAwait(false); - await (ExecuteActionsAsync(collectionCreations, cancellationToken)).ConfigureAwait(false); - await (ExecuteActionsAsync(deletions, cancellationToken)).ConfigureAwait(false); + try + { + await (ExecuteActionsAsync(insertions, cancellationToken)).ConfigureAwait(false); + await (ExecuteActionsAsync(updates, cancellationToken)).ConfigureAwait(false); + await (ExecuteActionsAsync(collectionRemovals, cancellationToken)).ConfigureAwait(false); + await (ExecuteActionsAsync(collectionUpdates, cancellationToken)).ConfigureAwait(false); + await (ExecuteActionsAsync(collectionCreations, cancellationToken)).ConfigureAwait(false); + await (ExecuteActionsAsync(deletions, cancellationToken)).ConfigureAwait(false); + } + finally + { + await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false); + } } private async Task PrepareActionsAsync(IList queue, CancellationToken cancellationToken) diff --git a/src/NHibernate/Async/Engine/Query/NativeSQLQueryPlan.cs b/src/NHibernate/Async/Engine/Query/NativeSQLQueryPlan.cs index 690fdc8af01..8e0aa8630a6 100644 --- a/src/NHibernate/Async/Engine/Query/NativeSQLQueryPlan.cs +++ b/src/NHibernate/Async/Engine/Query/NativeSQLQueryPlan.cs @@ -43,7 +43,7 @@ private async Task CoordinateSharedCacheCleanupAsync(ISessionImplementor session if (session.IsEventSource) { - await (((IEventSource)session).ActionQueue.AddActionAsync(action, cancellationToken)).ConfigureAwait(false); + ((IEventSource)session).ActionQueue.AddAction(action); } } diff --git a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs index 0a963d30894..5745bb121c0 100644 --- a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs +++ b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs @@ -43,7 +43,7 @@ protected virtual async Task CoordinateSharedCacheCleanupAsync(ISessionImplement if (session.IsEventSource) { - await (((IEventSource)session).ActionQueue.AddActionAsync(action, cancellationToken)).ConfigureAwait(false); + ((IEventSource) session).ActionQueue.AddAction(action); } } @@ -207,4 +207,4 @@ public async Task DoWorkAsync(DbConnection connection, DbTransaction transaction } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Cache/UpdateTimestampsCache.cs index 10c49e2b498..0507befae43 100644 --- a/src/NHibernate/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Cache/UpdateTimestampsCache.cs @@ -34,7 +34,7 @@ public UpdateTimestampsCache(Settings settings, IDictionary prop } [MethodImpl(MethodImplOptions.Synchronized)] - public void PreInvalidate(object[] spaces) + public virtual void PreInvalidate(object[] spaces) { //TODO: to handle concurrent writes correctly, this should return a Lock to the client long ts = updateTimestamps.NextTimestamp() + updateTimestamps.Timeout; @@ -47,7 +47,7 @@ public void PreInvalidate(object[] spaces) /// [MethodImpl(MethodImplOptions.Synchronized)] - public void Invalidate(object[] spaces) + public virtual void Invalidate(object[] spaces) { //TODO: to handle concurrent writes correctly, the client should pass in a Lock long ts = updateTimestamps.NextTimestamp(); @@ -60,7 +60,7 @@ public void Invalidate(object[] spaces) } [MethodImpl(MethodImplOptions.Synchronized)] - public bool IsUpToDate(ISet spaces, long timestamp /* H2.1 has Long here */) + public virtual bool IsUpToDate(ISet spaces, long timestamp /* H2.1 has Long here */) { foreach (string space in spaces) { @@ -95,7 +95,7 @@ public bool IsUpToDate(ISet spaces, long timestamp /* H2.1 has Long here return true; } - public void Destroy() + public virtual void Destroy() { try { @@ -107,4 +107,4 @@ public void Destroy() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Engine/ActionQueue.cs b/src/NHibernate/Engine/ActionQueue.cs index 1d2b7ef32d4..f138c05e279 100644 --- a/src/NHibernate/Engine/ActionQueue.cs +++ b/src/NHibernate/Engine/ActionQueue.cs @@ -3,7 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; - +using System.Threading; +using System.Threading.Tasks; using NHibernate.Action; using NHibernate.Cache; using NHibernate.Type; @@ -42,6 +43,7 @@ public partial class ActionQueue private readonly AfterTransactionCompletionProcessQueue afterTransactionProcesses; private readonly BeforeTransactionCompletionProcessQueue beforeTransactionProcesses; + private readonly HashSet executedSpaces; public ActionQueue(ISessionImplementor session) { @@ -56,6 +58,8 @@ public ActionQueue(ISessionImplementor session) afterTransactionProcesses = new AfterTransactionCompletionProcessQueue(session); beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue(session); + + executedSpaces = new HashSet(); } public virtual void Clear() @@ -108,7 +112,14 @@ public void AddAction(BulkOperationCleanupAction cleanupAction) { RegisterCleanupActions(cleanupAction); } - + + [Obsolete("This method is no longer executed asynchronously")] + public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, CancellationToken cancellationToken=default(CancellationToken)) + { + AddAction(cleanupAction); + return Task.CompletedTask; + } + public void RegisterProcess(AfterTransactionCompletionProcessDelegate process) { afterTransactionProcesses.Register(process); @@ -125,15 +136,43 @@ private void ExecuteActions(IList list) // It will then fail here due to list being modified. (Some previous code was dodging the // trouble with a for loop which was not failing provided the list was not getting smaller. // But then it was clearing it without having executed added actions (if any), ...) + foreach (IExecutable executable in list) - Execute(executable); - + { + InnerExecute(executable); + } list.Clear(); session.Batcher.ExecuteBatch(); + + } + + private void AfterExecutions() + { + if (session.Factory.Settings.IsQueryCacheEnabled) + { + session.Factory.UpdateTimestampsCache.PreInvalidate(executedSpaces.ToArray()); + } + executedSpaces.Clear(); } public void Execute(IExecutable executable) { + try + { + InnerExecute(executable); + } + finally + { + AfterExecutions(); + } + } + + private void InnerExecute(IExecutable executable) + { + if (executable.PropertySpaces != null) + { + executedSpaces.UnionWith(executable.PropertySpaces); + } try { executable.Execute(); @@ -143,7 +182,7 @@ public void Execute(IExecutable executable) RegisterCleanupActions(executable); } } - + private void RegisterCleanupActions(IExecutable executable) { beforeTransactionProcesses.Register(executable.BeforeTransactionCompletionProcess); @@ -151,7 +190,6 @@ private void RegisterCleanupActions(IExecutable executable) { string[] spaces = executable.PropertySpaces; afterTransactionProcesses.AddSpacesToInvalidate(spaces); - session.Factory.UpdateTimestampsCache.PreInvalidate(spaces); } afterTransactionProcesses.Register(executable.AfterTransactionCompletionProcess); } @@ -169,12 +207,19 @@ public void ExecuteInserts() /// public void ExecuteActions() { - ExecuteActions(insertions); - ExecuteActions(updates); - ExecuteActions(collectionRemovals); - ExecuteActions(collectionUpdates); - ExecuteActions(collectionCreations); - ExecuteActions(deletions); + try + { + ExecuteActions(insertions); + ExecuteActions(updates); + ExecuteActions(collectionRemovals); + ExecuteActions(collectionUpdates); + ExecuteActions(collectionCreations); + ExecuteActions(deletions); + } + finally + { + AfterExecutions(); + } } private void PrepareActions(IList queue) @@ -319,7 +364,7 @@ public void SortActions() private void SortInsertActions() { new InsertActionSorter(this).Sort(); - } + } public IList CloneDeletions() { From 616a1ddcbff9c3e4f5663ae41ea747caeba635e6 Mon Sep 17 00:00:00 2001 From: Gunnar Liljas Date: Sat, 2 Dec 2017 01:26:46 +0100 Subject: [PATCH 02/13] Fixed ExecuteInserts --- src/NHibernate/Async/Engine/ActionQueue.cs | 12 ++++++++---- src/NHibernate/Engine/ActionQueue.cs | 9 ++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/NHibernate/Async/Engine/ActionQueue.cs b/src/NHibernate/Async/Engine/ActionQueue.cs index 69eae6ab42e..88d7899e10f 100644 --- a/src/NHibernate/Async/Engine/ActionQueue.cs +++ b/src/NHibernate/Async/Engine/ActionQueue.cs @@ -85,13 +85,17 @@ private async Task InnerExecuteAsync(IExecutable executable, CancellationToken c /// Perform all currently queued entity-insertion actions. /// /// A cancellation token that can be used to cancel the work - public Task ExecuteInsertsAsync(CancellationToken cancellationToken) + public async Task ExecuteInsertsAsync(CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) + cancellationToken.ThrowIfCancellationRequested(); + try { - return Task.FromCanceled(cancellationToken); + await ExecuteActionsAsync(insertions, cancellationToken); + } + finally + { + await AfterExecutionsAsync(cancellationToken); } - return ExecuteActionsAsync(insertions, cancellationToken); } /// diff --git a/src/NHibernate/Engine/ActionQueue.cs b/src/NHibernate/Engine/ActionQueue.cs index f138c05e279..c414c8983fb 100644 --- a/src/NHibernate/Engine/ActionQueue.cs +++ b/src/NHibernate/Engine/ActionQueue.cs @@ -199,7 +199,14 @@ private void RegisterCleanupActions(IExecutable executable) /// public void ExecuteInserts() { - ExecuteActions(insertions); + try + { + ExecuteActions(insertions); + } + finally + { + AfterExecutions(); + } } /// From 38e569c60ddc97e5be561b51e39b890e5e33dcce Mon Sep 17 00:00:00 2001 From: Gunnar Liljas Date: Sun, 3 Dec 2017 20:39:08 +0100 Subject: [PATCH 03/13] Consolidated adding of queryspaces to be invalidated --- src/AsyncGenerator.yml | 2 ++ src/NHibernate/Async/Engine/ActionQueue.cs | 8 +++++--- .../Ast/ANTLR/Exec/AbstractStatementExecutor.cs | 4 ++-- src/NHibernate/Engine/ActionQueue.cs | 17 +++++++---------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index accaaeabe72..f259368c789 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -119,6 +119,8 @@ - name: GetFieldValue - name: IsDBNull - name: WriteLine + - name: AddAction + containingTypeName: ActionQueue callForwarding: true cancellationTokens: guards: true diff --git a/src/NHibernate/Async/Engine/ActionQueue.cs b/src/NHibernate/Async/Engine/ActionQueue.cs index 88d7899e10f..9fc0b51d210 100644 --- a/src/NHibernate/Async/Engine/ActionQueue.cs +++ b/src/NHibernate/Async/Engine/ActionQueue.cs @@ -46,7 +46,9 @@ private async Task AfterExecutionsAsync(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); if (session.Factory.Settings.IsQueryCacheEnabled) { - await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(executedSpaces.ToArray(), cancellationToken)).ConfigureAwait(false); + var spaces = executedSpaces.ToArray(); + afterTransactionProcesses.AddSpacesToInvalidate(spaces); + await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(spaces, cancellationToken)).ConfigureAwait(false); } executedSpaces.Clear(); } @@ -90,11 +92,11 @@ public async Task ExecuteInsertsAsync(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); try { - await ExecuteActionsAsync(insertions, cancellationToken); + await (ExecuteActionsAsync(insertions, cancellationToken)).ConfigureAwait(false); } finally { - await AfterExecutionsAsync(cancellationToken); + await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs index 5745bb121c0..ca8fb31f9af 100644 --- a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs +++ b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs @@ -43,7 +43,7 @@ protected virtual async Task CoordinateSharedCacheCleanupAsync(ISessionImplement if (session.IsEventSource) { - ((IEventSource) session).ActionQueue.AddAction(action); + ((IEventSource)session).ActionQueue.AddAction(action); } } @@ -207,4 +207,4 @@ public async Task DoWorkAsync(DbConnection connection, DbTransaction transaction } } } -} +} \ No newline at end of file diff --git a/src/NHibernate/Engine/ActionQueue.cs b/src/NHibernate/Engine/ActionQueue.cs index c414c8983fb..0a67a3697ca 100644 --- a/src/NHibernate/Engine/ActionQueue.cs +++ b/src/NHibernate/Engine/ActionQueue.cs @@ -150,7 +150,9 @@ private void AfterExecutions() { if (session.Factory.Settings.IsQueryCacheEnabled) { - session.Factory.UpdateTimestampsCache.PreInvalidate(executedSpaces.ToArray()); + var spaces = executedSpaces.ToArray(); + afterTransactionProcesses.AddSpacesToInvalidate(spaces); + session.Factory.UpdateTimestampsCache.PreInvalidate(spaces); } executedSpaces.Clear(); } @@ -169,16 +171,16 @@ public void Execute(IExecutable executable) private void InnerExecute(IExecutable executable) { - if (executable.PropertySpaces != null) - { - executedSpaces.UnionWith(executable.PropertySpaces); - } try { executable.Execute(); } finally { + if (executable.PropertySpaces != null) + { + executedSpaces.UnionWith(executable.PropertySpaces); + } RegisterCleanupActions(executable); } } @@ -186,11 +188,6 @@ private void InnerExecute(IExecutable executable) private void RegisterCleanupActions(IExecutable executable) { beforeTransactionProcesses.Register(executable.BeforeTransactionCompletionProcess); - if (session.Factory.Settings.IsQueryCacheEnabled) - { - string[] spaces = executable.PropertySpaces; - afterTransactionProcesses.AddSpacesToInvalidate(spaces); - } afterTransactionProcesses.Register(executable.AfterTransactionCompletionProcess); } From bcf49652c7fe354578be5cca2e923f99594f8a08 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Tue, 5 Dec 2017 13:59:11 +1300 Subject: [PATCH 04/13] Move invalidation of caches to ActionQueue --- .../SecondLevelCacheTest/InvalidationTests.cs | 15 ++-- .../Async/Cache/UpdateTimestampsCache.cs | 34 +++++-- src/NHibernate/Async/Engine/ActionQueue.cs | 89 +++++++++---------- src/NHibernate/Cache/UpdateTimestampsCache.cs | 25 ++++-- src/NHibernate/Engine/ActionQueue.cs | 84 ++++++----------- 5 files changed, 124 insertions(+), 123 deletions(-) diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index ce4bdb67aa7..b05ed9191c3 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -31,7 +31,9 @@ protected override void Configure(Configuration configuration) public void InvalidatesEntities() { var cache = Substitute.For(Sfi.Settings, new Dictionary()); - ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); + ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection( + x => x.UpdateTimestampsCache, + cache); var items = new List(); using (ISession session = OpenSession()) @@ -40,9 +42,10 @@ public void InvalidatesEntities() { foreach (var i in Enumerable.Range(1, 10)) { - var item = new Item { Id = i }; + var item = new Item {Id = i}; session.Save(item); } + tx.Commit(); } @@ -53,6 +56,7 @@ public void InvalidatesEntities() var item = session.Get(i); item.Name = item.Id.ToString(); } + tx.Commit(); } @@ -63,13 +67,14 @@ public void InvalidatesEntities() var item = session.Get(i); session.Delete(item); } + tx.Commit(); } } - //Should receive one preinvalidation and one invalidation per commit - cache.Received(3).PreInvalidate(Arg.Is(x => x.Length==1 && (string)x[0] == "Item")); - cache.Received(3).Invalidate(Arg.Is(x => x.Length == 1 && (string) x[0] == "Item")); + //Should receive one preinvalidation and one invalidation per commit + cache.Received(3).PreInvalidate(Arg.Is>(x => x.Count == 1 && (string) x.First() == "Item")); + cache.Received(3).Invalidate(Arg.Is>(x => x.Count == 1 && (string) x.First() == "Item")); } public void CleanUp() diff --git a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs index ffbf291be76..8cdb1d113f5 100644 --- a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs @@ -33,26 +33,46 @@ public Task ClearAsync(CancellationToken cancellationToken) return updateTimestamps.ClearAsync(cancellationToken); } + public virtual Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return PreInvalidateAsync((IReadOnlyList) spaces, cancellationToken); + } + [MethodImpl()] - public virtual async Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) + public virtual async Task PreInvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _preInvalidate.LockAsync()) { //TODO: to handle concurrent writes correctly, this should return a Lock to the client long ts = updateTimestamps.NextTimestamp() + updateTimestamps.Timeout; - for (int i = 0; i < spaces.Length; i++) + foreach (var space in spaces) { - await (updateTimestamps.PutAsync(spaces[i], ts, cancellationToken)).ConfigureAwait(false); + await (updateTimestamps.PutAsync(space, ts, cancellationToken)).ConfigureAwait(false); } + //TODO: return new Lock(ts); } + //TODO: return new Lock(ts); } /// + public Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InvalidateAsync((IReadOnlyList) spaces, cancellationToken); + } + [MethodImpl()] - public virtual async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken) + public virtual async Task InvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _invalidate.LockAsync()) @@ -60,10 +80,10 @@ public virtual async Task InvalidateAsync(object[] spaces, CancellationToken can //TODO: to handle concurrent writes correctly, the client should pass in a Lock long ts = updateTimestamps.NextTimestamp(); //TODO: if lock.getTimestamp().equals(ts) - for (int i = 0; i < spaces.Length; i++) + foreach (var space in spaces) { - log.Debug(string.Format("Invalidating space [{0}]", spaces[i])); - await (updateTimestamps.PutAsync(spaces[i], ts, cancellationToken)).ConfigureAwait(false); + log.Debug(string.Format("Invalidating space [{0}]", space)); + await (updateTimestamps.PutAsync(space, ts, cancellationToken)).ConfigureAwait(false); } } } diff --git a/src/NHibernate/Async/Engine/ActionQueue.cs b/src/NHibernate/Async/Engine/ActionQueue.cs index 9fc0b51d210..15a5951dbdd 100644 --- a/src/NHibernate/Async/Engine/ActionQueue.cs +++ b/src/NHibernate/Async/Engine/ActionQueue.cs @@ -38,19 +38,26 @@ private async Task ExecuteActionsAsync(IList list, CancellationToken cancellatio } list.Clear(); await (session.Batcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false); - } - private async Task AfterExecutionsAsync(CancellationToken cancellationToken) + private Task PreInvalidateCachesAsync(CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - if (session.Factory.Settings.IsQueryCacheEnabled) + if (cancellationToken.IsCancellationRequested) { - var spaces = executedSpaces.ToArray(); - afterTransactionProcesses.AddSpacesToInvalidate(spaces); - await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(spaces, cancellationToken)).ConfigureAwait(false); + return Task.FromCanceled(cancellationToken); + } + try + { + if (session.Factory.Settings.IsQueryCacheEnabled) + { + return session.Factory.UpdateTimestampsCache.PreInvalidateAsync(executedSpaces, cancellationToken); + } + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); } - executedSpaces.Clear(); } public async Task ExecuteAsync(IExecutable executable, CancellationToken cancellationToken) @@ -62,23 +69,23 @@ public async Task ExecuteAsync(IExecutable executable, CancellationToken cancell } finally { - await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false); + await (PreInvalidateCachesAsync(cancellationToken)).ConfigureAwait(false); } } private async Task InnerExecuteAsync(IExecutable executable, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (executable.PropertySpaces != null) - { - executedSpaces.UnionWith(executable.PropertySpaces); - } try { await (executable.ExecuteAsync(cancellationToken)).ConfigureAwait(false); } finally { + if (executable.PropertySpaces != null) + { + executedSpaces.UnionWith(executable.PropertySpaces); + } RegisterCleanupActions(executable); } } @@ -96,7 +103,7 @@ public async Task ExecuteInsertsAsync(CancellationToken cancellationToken) } finally { - await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false); + await (PreInvalidateCachesAsync(cancellationToken)).ConfigureAwait(false); } } @@ -118,11 +125,11 @@ public async Task ExecuteActionsAsync(CancellationToken cancellationToken) } finally { - await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false); + await (PreInvalidateCachesAsync(cancellationToken)).ConfigureAwait(false); } } - private async Task PrepareActionsAsync(IList queue, CancellationToken cancellationToken) + private static async Task PrepareActionsAsync(IList queue, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); foreach (IExecutable executable in queue) @@ -140,7 +147,7 @@ public async Task PrepareActionsAsync(CancellationToken cancellationToken) await (PrepareActionsAsync(collectionUpdates, cancellationToken)).ConfigureAwait(false); await (PrepareActionsAsync(collectionCreations, cancellationToken)).ConfigureAwait(false); } - + /// /// Performs cleanup of any held cache softlocks. /// @@ -152,41 +159,27 @@ public Task AfterTransactionCompletionAsync(bool success, CancellationToken canc { return Task.FromCanceled(cancellationToken); } - return afterTransactionProcesses.AfterTransactionCompletionAsync(success, cancellationToken); + try + { + afterTransactionProcesses.AfterTransactionCompletion(success); + + return InvalidateCachesAsync(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } - private partial class AfterTransactionCompletionProcessQueue + + private async Task InvalidateCachesAsync(CancellationToken cancellationToken) { - - public async Task AfterTransactionCompletionAsync(bool success, CancellationToken cancellationToken) + cancellationToken.ThrowIfCancellationRequested(); + if (session.Factory.Settings.IsQueryCacheEnabled) { - cancellationToken.ThrowIfCancellationRequested(); - int size = processes.Count; - - for (int i = 0; i < size; i++) - { - try - { - AfterTransactionCompletionProcessDelegate process = processes[i]; - process(success); - } - catch (CacheException e) - { - log.Error( "could not release a cache lock", e); - // continue loop - } - catch (Exception e) - { - throw new AssertionFailure("Unable to perform AfterTransactionCompletion callback", e); - } - } - processes.Clear(); - - if (session.Factory.Settings.IsQueryCacheEnabled) - { - await (session.Factory.UpdateTimestampsCache.InvalidateAsync(querySpacesToInvalidate.ToArray(), cancellationToken)).ConfigureAwait(false); - } - querySpacesToInvalidate.Clear(); + await (session.Factory.UpdateTimestampsCache.InvalidateAsync(executedSpaces, cancellationToken)).ConfigureAwait(false); } + + executedSpaces.Clear(); } } } diff --git a/src/NHibernate/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Cache/UpdateTimestampsCache.cs index 0507befae43..c8e4bf3bc96 100644 --- a/src/NHibernate/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Cache/UpdateTimestampsCache.cs @@ -33,29 +33,40 @@ public UpdateTimestampsCache(Settings settings, IDictionary prop updateTimestamps = settings.CacheProvider.BuildCache(regionName, props); } + public void PreInvalidate(object[] spaces) + { + PreInvalidate((IReadOnlyList) spaces); + } + [MethodImpl(MethodImplOptions.Synchronized)] - public virtual void PreInvalidate(object[] spaces) + public virtual void PreInvalidate(IReadOnlyCollection spaces) { //TODO: to handle concurrent writes correctly, this should return a Lock to the client long ts = updateTimestamps.NextTimestamp() + updateTimestamps.Timeout; - for (int i = 0; i < spaces.Length; i++) + foreach (var space in spaces) { - updateTimestamps.Put(spaces[i], ts); + updateTimestamps.Put(space, ts); } + //TODO: return new Lock(ts); } /// + public void Invalidate(object[] spaces) + { + Invalidate((IReadOnlyList) spaces); + } + [MethodImpl(MethodImplOptions.Synchronized)] - public virtual void Invalidate(object[] spaces) + public virtual void Invalidate(IReadOnlyCollection spaces) { //TODO: to handle concurrent writes correctly, the client should pass in a Lock long ts = updateTimestamps.NextTimestamp(); //TODO: if lock.getTimestamp().equals(ts) - for (int i = 0; i < spaces.Length; i++) + foreach (var space in spaces) { - log.Debug(string.Format("Invalidating space [{0}]", spaces[i])); - updateTimestamps.Put(spaces[i], ts); + log.Debug(string.Format("Invalidating space [{0}]", space)); + updateTimestamps.Put(space, ts); } } diff --git a/src/NHibernate/Engine/ActionQueue.cs b/src/NHibernate/Engine/ActionQueue.cs index 0a67a3697ca..c7cf2a65e8c 100644 --- a/src/NHibernate/Engine/ActionQueue.cs +++ b/src/NHibernate/Engine/ActionQueue.cs @@ -56,8 +56,8 @@ public ActionQueue(ISessionImplementor session) collectionUpdates = new List(InitQueueListSize); collectionRemovals = new List(InitQueueListSize); - afterTransactionProcesses = new AfterTransactionCompletionProcessQueue(session); - beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue(session); + afterTransactionProcesses = new AfterTransactionCompletionProcessQueue(); + beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue(); executedSpaces = new HashSet(); } @@ -143,18 +143,14 @@ private void ExecuteActions(IList list) } list.Clear(); session.Batcher.ExecuteBatch(); - } - private void AfterExecutions() + private void PreInvalidateCaches() { if (session.Factory.Settings.IsQueryCacheEnabled) { - var spaces = executedSpaces.ToArray(); - afterTransactionProcesses.AddSpacesToInvalidate(spaces); - session.Factory.UpdateTimestampsCache.PreInvalidate(spaces); + session.Factory.UpdateTimestampsCache.PreInvalidate(executedSpaces); } - executedSpaces.Clear(); } public void Execute(IExecutable executable) @@ -165,7 +161,7 @@ public void Execute(IExecutable executable) } finally { - AfterExecutions(); + PreInvalidateCaches(); } } @@ -202,7 +198,7 @@ public void ExecuteInserts() } finally { - AfterExecutions(); + PreInvalidateCaches(); } } @@ -222,11 +218,11 @@ public void ExecuteActions() } finally { - AfterExecutions(); + PreInvalidateCaches(); } } - private void PrepareActions(IList queue) + private static void PrepareActions(IList queue) { foreach (IExecutable executable in queue) executable.BeforeExecutions(); @@ -249,7 +245,7 @@ public void BeforeTransactionCompletion() { beforeTransactionProcesses.BeforeTransactionCompletion(); } - + /// /// Performs cleanup of any held cache softlocks. /// @@ -257,8 +253,20 @@ public void BeforeTransactionCompletion() public void AfterTransactionCompletion(bool success) { afterTransactionProcesses.AfterTransactionCompletion(success); + + InvalidateCaches(); } - + + private void InvalidateCaches() + { + if (session.Factory.Settings.IsQueryCacheEnabled) + { + session.Factory.UpdateTimestampsCache.Invalidate(executedSpaces); + } + + executedSpaces.Clear(); + } + /// /// Check whether the given tables/query-spaces are to be executed against /// given the currently queued actions. @@ -434,19 +442,13 @@ public override string ToString() [Serializable] private class BeforeTransactionCompletionProcessQueue { - private ISessionImplementor session; - private IList processes = new List(); + private List processes = new List(); public bool HasActions { get { return processes.Count > 0; } } - public BeforeTransactionCompletionProcessQueue(ISessionImplementor session) - { - this.session = session; - } - public void Register(BeforeTransactionCompletionProcessDelegate process) { if (process == null) @@ -466,9 +468,9 @@ public void BeforeTransactionCompletion() BeforeTransactionCompletionProcessDelegate process = processes[i]; process(); } - catch (HibernateException e) + catch (HibernateException) { - throw e; + throw; } catch (Exception e) { @@ -480,39 +482,15 @@ public void BeforeTransactionCompletion() } [Serializable] - private partial class AfterTransactionCompletionProcessQueue + private class AfterTransactionCompletionProcessQueue { - private ISessionImplementor session; - private HashSet querySpacesToInvalidate = new HashSet(); - private IList processes = new List(InitQueueListSize * 3); + private List processes = new List(InitQueueListSize * 3); public bool HasActions { get { return processes.Count > 0; } } - - public AfterTransactionCompletionProcessQueue(ISessionImplementor session) - { - this.session = session; - } - - public void AddSpacesToInvalidate(string[] spaces) - { - if (spaces == null) - { - return; - } - for (int i = 0, max = spaces.Length; i < max; i++) - { - this.AddSpaceToInvalidate(spaces[i]); - } - } - - public void AddSpaceToInvalidate(string space) - { - querySpacesToInvalidate.Add(space); - } - + public void Register(AfterTransactionCompletionProcessDelegate process) { if (process == null) @@ -544,12 +522,6 @@ public void AfterTransactionCompletion(bool success) } } processes.Clear(); - - if (session.Factory.Settings.IsQueryCacheEnabled) - { - session.Factory.UpdateTimestampsCache.Invalidate(querySpacesToInvalidate.ToArray()); - } - querySpacesToInvalidate.Clear(); } } From 7b0469686133fca752b97dc65082b3e69ebf0917 Mon Sep 17 00:00:00 2001 From: Gunnar Liljas Date: Wed, 6 Dec 2017 23:54:25 +0100 Subject: [PATCH 05/13] Stronger typing of invalidation methods. Fixed assertions in unit test. --- .../SecondLevelCacheTest/InvalidationTests.cs | 33 ++++++++++++++----- src/NHibernate/Cache/UpdateTimestampsCache.cs | 14 +++++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index b05ed9191c3..205e3714a19 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -1,15 +1,12 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Impl; using NHibernate.Test.SecondLevelCacheTests; using NSubstitute; using NUnit.Framework; -using Environment = System.Environment; namespace NHibernate.Test.SecondLevelCacheTest { @@ -23,8 +20,8 @@ public class InvalidationTests : TestCase protected override void Configure(Configuration configuration) { base.Configure(configuration); - configuration.Properties[Cfg.Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; - configuration.Properties[Cfg.Environment.UseQueryCache] = "true"; + configuration.Properties[Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; + configuration.Properties[Environment.UseQueryCache] = "true"; } [Test] @@ -35,7 +32,18 @@ public void InvalidatesEntities() x => x.UpdateTimestampsCache, cache); - var items = new List(); + //"Received" assertions can not be used since the collection is reused and cleared between calls. + //The received args are cloned and stored + var preInvalidations = new List>(); + var invalidations = new List>(); + + cache + .When(x=>x.PreInvalidate(Arg.Any>())) + .Do(x=>preInvalidations.Add(((IReadOnlyCollection) x[0]).ToList())); + cache + .When(x => x.Invalidate(Arg.Any>())) + .Do(x => invalidations.Add(((IReadOnlyCollection) x[0]).ToList())); + using (ISession session = OpenSession()) { using (ITransaction tx = session.BeginTransaction()) @@ -73,8 +81,17 @@ public void InvalidatesEntities() } //Should receive one preinvalidation and one invalidation per commit - cache.Received(3).PreInvalidate(Arg.Is>(x => x.Count == 1 && (string) x.First() == "Item")); - cache.Received(3).Invalidate(Arg.Is>(x => x.Count == 1 && (string) x.First() == "Item")); + Assert.That(preInvalidations.Count,Is.EqualTo(3)); + Assert.That(preInvalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); + + Assert.That(invalidations.Count, Is.EqualTo(3)); + Assert.That(invalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); + + } + + private bool IsRight(HashSet x) + { + return x.Count == 1 && x.First() == "Item"; } public void CleanUp() diff --git a/src/NHibernate/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Cache/UpdateTimestampsCache.cs index c8e4bf3bc96..3f0296dfc84 100644 --- a/src/NHibernate/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Cache/UpdateTimestampsCache.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using NHibernate.Cfg; @@ -33,13 +34,15 @@ public UpdateTimestampsCache(Settings settings, IDictionary prop updateTimestamps = settings.CacheProvider.BuildCache(regionName, props); } + //TODO: Make obsolete public void PreInvalidate(object[] spaces) { - PreInvalidate((IReadOnlyList) spaces); + //Only for backwards compatibility. + PreInvalidate(spaces.OfType().ToList()); } [MethodImpl(MethodImplOptions.Synchronized)] - public virtual void PreInvalidate(IReadOnlyCollection spaces) + public virtual void PreInvalidate(IReadOnlyCollection spaces) { //TODO: to handle concurrent writes correctly, this should return a Lock to the client long ts = updateTimestamps.NextTimestamp() + updateTimestamps.Timeout; @@ -51,14 +54,15 @@ public virtual void PreInvalidate(IReadOnlyCollection spaces) //TODO: return new Lock(ts); } - /// + //TODO: Make obsolete public void Invalidate(object[] spaces) { - Invalidate((IReadOnlyList) spaces); + //Only for backwards compatibility. + Invalidate(spaces.OfType().ToList()); } [MethodImpl(MethodImplOptions.Synchronized)] - public virtual void Invalidate(IReadOnlyCollection spaces) + public virtual void Invalidate(IReadOnlyCollection spaces) { //TODO: to handle concurrent writes correctly, the client should pass in a Lock long ts = updateTimestamps.NextTimestamp(); From b0c5aa2c835deb037d441ef5bb81ef498e3c7647 Mon Sep 17 00:00:00 2001 From: Gunnar Liljas Date: Thu, 7 Dec 2017 00:19:20 +0100 Subject: [PATCH 06/13] Async unit test and invalidation --- .../SecondLevelCacheTest/InvalidationTests.cs | 43 ++++++++++++++----- .../Async/Cache/UpdateTimestampsCache.cs | 30 ++++++++++--- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index 41103e97e5e..781ab92c79e 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -11,18 +11,16 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Impl; using NHibernate.Test.SecondLevelCacheTests; using NSubstitute; using NUnit.Framework; -using Environment = System.Environment; namespace NHibernate.Test.SecondLevelCacheTest { + using System.Threading.Tasks; using System.Threading; [TestFixture] public class InvalidationTestsAsync : TestCase @@ -34,26 +32,40 @@ public class InvalidationTestsAsync : TestCase protected override void Configure(Configuration configuration) { base.Configure(configuration); - configuration.Properties[Cfg.Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; - configuration.Properties[Cfg.Environment.UseQueryCache] = "true"; + configuration.Properties[Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; + configuration.Properties[Environment.UseQueryCache] = "true"; } [Test] public async Task InvalidatesEntitiesAsync() { var cache = Substitute.For(Sfi.Settings, new Dictionary()); - ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); + ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection( + x => x.UpdateTimestampsCache, + cache); + + //"Received" assertions can not be used since the collection is reused and cleared between calls. + //The received args are cloned and stored + var preInvalidations = new List>(); + var invalidations = new List>(); + + cache + .When(x=>x.PreInvalidate(Arg.Any>())) + .Do(x=>preInvalidations.Add(((IReadOnlyCollection) x[0]).ToList())); + cache + .When(x => x.Invalidate(Arg.Any>())) + .Do(x => invalidations.Add(((IReadOnlyCollection) x[0]).ToList())); - var items = new List(); using (ISession session = OpenSession()) { using (ITransaction tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { - var item = new Item { Id = i }; + var item = new Item {Id = i}; await (session.SaveAsync(item)); } + await (tx.CommitAsync()); } @@ -64,6 +76,7 @@ public async Task InvalidatesEntitiesAsync() var item = await (session.GetAsync(i)); item.Name = item.Id.ToString(); } + await (tx.CommitAsync()); } @@ -74,15 +87,25 @@ public async Task InvalidatesEntitiesAsync() var item = await (session.GetAsync(i)); await (session.DeleteAsync(item)); } + await (tx.CommitAsync()); } } + //Should receive one preinvalidation and one invalidation per commit - await (cache.Received(3).PreInvalidateAsync(Arg.Is(x => x.Length==1 && (string)x[0] == "Item"), CancellationToken.None)); - await (cache.Received(3).InvalidateAsync(Arg.Is(x => x.Length == 1 && (string) x[0] == "Item"), CancellationToken.None)); + Assert.That(preInvalidations.Count,Is.EqualTo(3)); + Assert.That(preInvalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); + + Assert.That(invalidations.Count, Is.EqualTo(3)); + Assert.That(invalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); } + private bool IsRight(HashSet x) + { + return x.Count == 1 && x.First() == "Item"; + } + public async Task CleanUpAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (ISession s = OpenSession()) diff --git a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs index 8cdb1d113f5..1cb96f8aa1c 100644 --- a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using NHibernate.Cfg; @@ -33,17 +34,26 @@ public Task ClearAsync(CancellationToken cancellationToken) return updateTimestamps.ClearAsync(cancellationToken); } - public virtual Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) + //TODO: Make obsolete + public Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return PreInvalidateAsync((IReadOnlyList) spaces, cancellationToken); + try + { + //Only for backwards compatibility. + return PreInvalidateAsync(spaces.OfType().ToList(), cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } [MethodImpl()] - public virtual async Task PreInvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) + public virtual async Task PreInvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _preInvalidate.LockAsync()) @@ -61,18 +71,26 @@ public virtual async Task PreInvalidateAsync(IReadOnlyCollection spaces, //TODO: return new Lock(ts); } - /// + //TODO: Make obsolete public Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return InvalidateAsync((IReadOnlyList) spaces, cancellationToken); + try + { + //Only for backwards compatibility. + return InvalidateAsync(spaces.OfType().ToList(), cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } } [MethodImpl()] - public virtual async Task InvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) + public virtual async Task InvalidateAsync(IReadOnlyCollection spaces, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (await _invalidate.LockAsync()) From 7e3b9fc59b84142677ca0b2fddb5b09049a92d6b Mon Sep 17 00:00:00 2001 From: Gunnar Liljas Date: Thu, 7 Dec 2017 00:27:23 +0100 Subject: [PATCH 07/13] Debug method removed --- .../Async/SecondLevelCacheTest/InvalidationTests.cs | 9 ++------- .../SecondLevelCacheTest/InvalidationTests.cs | 7 +------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index 781ab92c79e..d8b74a77acb 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -100,12 +100,7 @@ public async Task InvalidatesEntitiesAsync() Assert.That(invalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); } - - private bool IsRight(HashSet x) - { - return x.Count == 1 && x.First() == "Item"; - } - + public async Task CleanUpAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (ISession s = OpenSession()) @@ -115,7 +110,7 @@ private bool IsRight(HashSet x) await (tx.CommitAsync(cancellationToken)); } } - + public void CleanUp() { using (ISession s = OpenSession()) diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index 205e3714a19..e6268dae7c6 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -88,12 +88,7 @@ public void InvalidatesEntities() Assert.That(invalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); } - - private bool IsRight(HashSet x) - { - return x.Count == 1 && x.First() == "Item"; - } - + public void CleanUp() { using (ISession s = OpenSession()) From 4f24faac1037b00f8056756af4d3b5b1c4a8ac12 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Thu, 7 Dec 2017 15:55:33 +1300 Subject: [PATCH 08/13] Mark methods of UpdateTimestapsCache obsolete --- src/AsyncGenerator.yml | 12 ++++++++---- .../Async/Cache/UpdateTimestampsCache.cs | 6 ++++-- src/NHibernate/Cache/UpdateTimestampsCache.cs | 6 ++++-- src/NHibernate/Engine/ActionQueue.cs | 18 +++++++++++++++--- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index f259368c789..7022d322eb6 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -4,8 +4,6 @@ applyChanges: true analyzation: methodConversion: - - conversion: Ignore - hasAttributeName: ObsoleteAttribute - conversion: Ignore name: PostProcessInsert containingTypeName: HqlSqlWalker @@ -96,6 +94,9 @@ - conversion: Ignore name: Exists containingTypeName: AbstractCollectionPersister + - conversion: Ignore + name: QuoteTableAndColumns + containingTypeName: SchemaMetadataUpdater - conversion: ToAsync name: ExecuteReader containingTypeName: IBatcher @@ -119,8 +120,8 @@ - name: GetFieldValue - name: IsDBNull - name: WriteLine - - name: AddAction - containingTypeName: ActionQueue + ignoreAsyncCounterparts: + - rule: Obsolete callForwarding: true cancellationTokens: guards: true @@ -261,6 +262,9 @@ methodRules: - containingType: NHibernate.Tool.hbm2ddl.SchemaValidator - containingType: NHibernate.Tool.hbm2ddl.SchemaExport name: PubliclyExposedType +- filters: + - hasAttributeName: ObsoleteAttribute + name: Obsolete typeRules: - filters: - containingAssemblyName: NHibernate diff --git a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs index 23982e815ac..34dc40aaf69 100644 --- a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs @@ -34,7 +34,8 @@ public Task ClearAsync(CancellationToken cancellationToken) return updateTimestamps.ClearAsync(cancellationToken); } - //TODO: Make obsolete + //Since v5.1 + [Obsolete("Please use PreInvalidate(IReadOnlyCollection) instead.")] public Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -71,7 +72,8 @@ public virtual async Task PreInvalidateAsync(IReadOnlyCollection spaces, //TODO: return new Lock(ts); } - //TODO: Make obsolete + //Since v5.1 + [Obsolete("Please use PreInvalidate(IReadOnlyCollection) instead.")] public Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) diff --git a/src/NHibernate/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Cache/UpdateTimestampsCache.cs index 8db8d078f93..89f2efd28cd 100644 --- a/src/NHibernate/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Cache/UpdateTimestampsCache.cs @@ -34,7 +34,8 @@ public UpdateTimestampsCache(Settings settings, IDictionary prop updateTimestamps = settings.CacheProvider.BuildCache(regionName, props); } - //TODO: Make obsolete + //Since v5.1 + [Obsolete("Please use PreInvalidate(IReadOnlyCollection) instead.")] public void PreInvalidate(object[] spaces) { //Only for backwards compatibility. @@ -54,7 +55,8 @@ public virtual void PreInvalidate(IReadOnlyCollection spaces) //TODO: return new Lock(ts); } - //TODO: Make obsolete + //Since v5.1 + [Obsolete("Please use PreInvalidate(IReadOnlyCollection) instead.")] public void Invalidate(object[] spaces) { //Only for backwards compatibility. diff --git a/src/NHibernate/Engine/ActionQueue.cs b/src/NHibernate/Engine/ActionQueue.cs index 822094d7c42..d4d8344000a 100644 --- a/src/NHibernate/Engine/ActionQueue.cs +++ b/src/NHibernate/Engine/ActionQueue.cs @@ -113,11 +113,23 @@ public void AddAction(BulkOperationCleanupAction cleanupAction) RegisterCleanupActions(cleanupAction); } - [Obsolete("This method is no longer executed asynchronously")] + //Since v5.1 + [Obsolete("This method is no longer executed asynchronously and will be removed in a next major version.")] public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, CancellationToken cancellationToken=default(CancellationToken)) { - AddAction(cleanupAction); - return Task.CompletedTask; + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + AddAction(cleanupAction); + return Task.CompletedTask; + } + catch (Exception e) + { + return Task.FromException(e); + } } public void RegisterProcess(AfterTransactionCompletionProcessDelegate process) From 9607294e2a068d8380f0bc7b22bb40d7a53758b9 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Thu, 7 Dec 2017 16:15:59 +1300 Subject: [PATCH 09/13] Fix async tests --- .../Async/SecondLevelCacheTest/InvalidationTests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index d8b74a77acb..ee666832780 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -49,12 +49,13 @@ public async Task InvalidatesEntitiesAsync() var preInvalidations = new List>(); var invalidations = new List>(); - cache - .When(x=>x.PreInvalidate(Arg.Any>())) - .Do(x=>preInvalidations.Add(((IReadOnlyCollection) x[0]).ToList())); - cache - .When(x => x.Invalidate(Arg.Any>())) - .Do(x => invalidations.Add(((IReadOnlyCollection) x[0]).ToList())); + await cache.PreInvalidateAsync( + Arg.Do>(x => preInvalidations.Add(x.ToList())), + CancellationToken.None); + + await cache.InvalidateAsync( + Arg.Do>(x => invalidations.Add(x.ToList())), + CancellationToken.None); using (ISession session = OpenSession()) { From 63dc8cdf76c55992892a416c36ad0505d6b88cbe Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Thu, 7 Dec 2017 16:37:31 +1300 Subject: [PATCH 10/13] Adjust tests to ensure generated async tests pass --- .../SecondLevelCacheTest/InvalidationTests.cs | 62 ++++++------------- .../SecondLevelCacheTest/InvalidationTests.cs | 51 ++++++--------- 2 files changed, 40 insertions(+), 73 deletions(-) diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index ee666832780..1caf7214516 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -31,35 +31,30 @@ public class InvalidationTestsAsync : TestCase protected override void Configure(Configuration configuration) { - base.Configure(configuration); - configuration.Properties[Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; - configuration.Properties[Environment.UseQueryCache] = "true"; + configuration.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName); + configuration.SetProperty(Environment.UseQueryCache, "true"); } [Test] public async Task InvalidatesEntitiesAsync() { + var debugSessionFactory = (DebugSessionFactory) Sfi; + var sessionFactoryImpl = (SessionFactoryImpl) debugSessionFactory.ActualFactory; + var cache = Substitute.For(Sfi.Settings, new Dictionary()); - ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection( - x => x.UpdateTimestampsCache, - cache); + sessionFactoryImpl.SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); //"Received" assertions can not be used since the collection is reused and cleared between calls. //The received args are cloned and stored var preInvalidations = new List>(); var invalidations = new List>(); - await cache.PreInvalidateAsync( - Arg.Do>(x => preInvalidations.Add(x.ToList())), - CancellationToken.None); - - await cache.InvalidateAsync( - Arg.Do>(x => invalidations.Add(x.ToList())), - CancellationToken.None); + await (cache.PreInvalidateAsync(Arg.Do>(x => preInvalidations.Add(x.ToList())), CancellationToken.None)); + await (cache.InvalidateAsync(Arg.Do>(x => invalidations.Add(x.ToList())), CancellationToken.None)); - using (ISession session = OpenSession()) + using (var session = OpenSession()) { - using (ITransaction tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { @@ -70,7 +65,7 @@ await cache.InvalidateAsync( await (tx.CommitAsync()); } - using (ITransaction tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { @@ -81,7 +76,7 @@ await cache.InvalidateAsync( await (tx.CommitAsync()); } - using (ITransaction tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { @@ -94,38 +89,21 @@ await cache.InvalidateAsync( } //Should receive one preinvalidation and one invalidation per commit - Assert.That(preInvalidations.Count,Is.EqualTo(3)); - Assert.That(preInvalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); + Assert.That(preInvalidations, Has.Count.EqualTo(3)); + Assert.That(preInvalidations, Has.All.Count.EqualTo(1).And.Contains("Item")); - Assert.That(invalidations.Count, Is.EqualTo(3)); - Assert.That(invalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); - - } - - public async Task CleanUpAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) - { - await (s.DeleteAsync("from Item", cancellationToken)); - await (tx.CommitAsync(cancellationToken)); - } + Assert.That(invalidations, Has.Count.EqualTo(3)); + Assert.That(invalidations, Has.All.Count.EqualTo(1).And.Contains("Item")); } - - public void CleanUp() + + protected override void OnTearDown() { - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) { s.Delete("from Item"); tx.Commit(); } } - - protected override void OnTearDown() - { - CleanUp(); - base.OnTearDown(); - } } } diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index e6268dae7c6..a14e67181f9 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -19,34 +19,30 @@ public class InvalidationTests : TestCase protected override void Configure(Configuration configuration) { - base.Configure(configuration); - configuration.Properties[Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName; - configuration.Properties[Environment.UseQueryCache] = "true"; + configuration.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName); + configuration.SetProperty(Environment.UseQueryCache, "true"); } [Test] public void InvalidatesEntities() { + var debugSessionFactory = (DebugSessionFactory) Sfi; + var sessionFactoryImpl = (SessionFactoryImpl) debugSessionFactory.ActualFactory; + var cache = Substitute.For(Sfi.Settings, new Dictionary()); - ((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection( - x => x.UpdateTimestampsCache, - cache); + sessionFactoryImpl.SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); //"Received" assertions can not be used since the collection is reused and cleared between calls. //The received args are cloned and stored var preInvalidations = new List>(); var invalidations = new List>(); - cache - .When(x=>x.PreInvalidate(Arg.Any>())) - .Do(x=>preInvalidations.Add(((IReadOnlyCollection) x[0]).ToList())); - cache - .When(x => x.Invalidate(Arg.Any>())) - .Do(x => invalidations.Add(((IReadOnlyCollection) x[0]).ToList())); + cache.PreInvalidate(Arg.Do>(x => preInvalidations.Add(x.ToList()))); + cache.Invalidate(Arg.Do>(x => invalidations.Add(x.ToList()))); - using (ISession session = OpenSession()) + using (var session = OpenSession()) { - using (ITransaction tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { @@ -57,7 +53,7 @@ public void InvalidatesEntities() tx.Commit(); } - using (ITransaction tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { @@ -68,7 +64,7 @@ public void InvalidatesEntities() tx.Commit(); } - using (ITransaction tx = session.BeginTransaction()) + using (var tx = session.BeginTransaction()) { foreach (var i in Enumerable.Range(1, 10)) { @@ -81,28 +77,21 @@ public void InvalidatesEntities() } //Should receive one preinvalidation and one invalidation per commit - Assert.That(preInvalidations.Count,Is.EqualTo(3)); - Assert.That(preInvalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); - - Assert.That(invalidations.Count, Is.EqualTo(3)); - Assert.That(invalidations.All(x => x.Count == 1 && x.First() == "Item"), Is.True); + Assert.That(preInvalidations, Has.Count.EqualTo(3)); + Assert.That(preInvalidations, Has.All.Count.EqualTo(1).And.Contains("Item")); + Assert.That(invalidations, Has.Count.EqualTo(3)); + Assert.That(invalidations, Has.All.Count.EqualTo(1).And.Contains("Item")); } - - public void CleanUp() + + protected override void OnTearDown() { - using (ISession s = OpenSession()) - using (ITransaction tx = s.BeginTransaction()) + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) { s.Delete("from Item"); tx.Commit(); } } - - protected override void OnTearDown() - { - CleanUp(); - base.OnTearDown(); - } } } From 181bba192e28a3fea4a567041d16505e41bac27f Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Fri, 8 Dec 2017 11:03:36 +1300 Subject: [PATCH 11/13] Make Clear/ClearAsync virtual --- src/NHibernate/Async/Cache/UpdateTimestampsCache.cs | 2 +- src/NHibernate/Cache/UpdateTimestampsCache.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs index 34dc40aaf69..9a086f16333 100644 --- a/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Async/Cache/UpdateTimestampsCache.cs @@ -25,7 +25,7 @@ public partial class UpdateTimestampsCache private readonly NHibernate.Util.AsyncLock _invalidate = new NHibernate.Util.AsyncLock(); private readonly NHibernate.Util.AsyncLock _isUpToDate = new NHibernate.Util.AsyncLock(); - public Task ClearAsync(CancellationToken cancellationToken) + public virtual Task ClearAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { diff --git a/src/NHibernate/Cache/UpdateTimestampsCache.cs b/src/NHibernate/Cache/UpdateTimestampsCache.cs index 89f2efd28cd..450ea3ce469 100644 --- a/src/NHibernate/Cache/UpdateTimestampsCache.cs +++ b/src/NHibernate/Cache/UpdateTimestampsCache.cs @@ -21,7 +21,7 @@ public partial class UpdateTimestampsCache private readonly string regionName = typeof(UpdateTimestampsCache).Name; - public void Clear() + public virtual void Clear() { updateTimestamps.Clear(); } From 298bb226250bd0e1f018371cc5227d6f5f8fc3fd Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Fri, 8 Dec 2017 11:04:04 +1300 Subject: [PATCH 12/13] Remove TestExtensions --- .../SecondLevelCacheTest/InvalidationTests.cs | 7 ++-- .../SecondLevelCacheTest/InvalidationTests.cs | 5 +-- src/NHibernate.Test/TestExtensions.cs | 32 ------------------- 3 files changed, 8 insertions(+), 36 deletions(-) delete mode 100644 src/NHibernate.Test/TestExtensions.cs diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index 1caf7214516..45b8b6f0bb4 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -11,8 +11,10 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using NHibernate.Cache; using NHibernate.Cfg; +using NHibernate.Engine; using NHibernate.Impl; using NHibernate.Test.SecondLevelCacheTests; using NSubstitute; @@ -39,10 +41,11 @@ protected override void Configure(Configuration configuration) public async Task InvalidatesEntitiesAsync() { var debugSessionFactory = (DebugSessionFactory) Sfi; - var sessionFactoryImpl = (SessionFactoryImpl) debugSessionFactory.ActualFactory; var cache = Substitute.For(Sfi.Settings, new Dictionary()); - sessionFactoryImpl.SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); + + var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField("updateTimestampsCache"); + updateTimestampsCacheField.SetValue(debugSessionFactory.ActualFactory, cache); //"Received" assertions can not be used since the collection is reused and cleared between calls. //The received args are cloned and stored diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index a14e67181f9..7d15e8d9f6a 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -27,10 +27,11 @@ protected override void Configure(Configuration configuration) public void InvalidatesEntities() { var debugSessionFactory = (DebugSessionFactory) Sfi; - var sessionFactoryImpl = (SessionFactoryImpl) debugSessionFactory.ActualFactory; var cache = Substitute.For(Sfi.Settings, new Dictionary()); - sessionFactoryImpl.SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache); + + var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField("updateTimestampsCache"); + updateTimestampsCacheField.SetValue(debugSessionFactory.ActualFactory, cache); //"Received" assertions can not be used since the collection is reused and cleared between calls. //The received args are cloned and stored diff --git a/src/NHibernate.Test/TestExtensions.cs b/src/NHibernate.Test/TestExtensions.cs deleted file mode 100644 index 14dc253c464..00000000000 --- a/src/NHibernate.Test/TestExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; - -namespace NHibernate.Test -{ - //Utility extension methods for use in unit tests. - public static class TestExtensions - { - public static T SetPropertyUsingReflection(this T instance, Expression> property, TProp value) - { - var method = property.Body as MemberExpression; - var propertyInfo = typeof(T).GetProperty(method.Member.Name); - if (propertyInfo!= null && propertyInfo.CanWrite) - { - propertyInfo.SetValue(instance, value); - } - else - { - //camel cased field - var name = method.Member.Name.Substring(0, 1).ToLowerInvariant() + method.Member.Name.Substring(1); - var field = typeof(T).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); - if (field != null) - { - field.SetValue(instance, value); - } - } - return instance; - } - - } -} From 93ae332a8270fdcbfc56e86a024898e0940bd71a Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Fri, 8 Dec 2017 11:52:15 +1300 Subject: [PATCH 13/13] Fix getting updateTimestampsCache field --- .../Async/SecondLevelCacheTest/InvalidationTests.cs | 5 ++++- .../SecondLevelCacheTest/InvalidationTests.cs | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs index 45b8b6f0bb4..76e5b040cbb 100644 --- a/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs @@ -44,7 +44,10 @@ public async Task InvalidatesEntitiesAsync() var cache = Substitute.For(Sfi.Settings, new Dictionary()); - var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField("updateTimestampsCache"); + var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField( + "updateTimestampsCache", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + updateTimestampsCacheField.SetValue(debugSessionFactory.ActualFactory, cache); //"Received" assertions can not be used since the collection is reused and cleared between calls. diff --git a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs index 7d15e8d9f6a..050a488bc1e 100644 --- a/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs +++ b/src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Impl; @@ -30,7 +31,10 @@ public void InvalidatesEntities() var cache = Substitute.For(Sfi.Settings, new Dictionary()); - var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField("updateTimestampsCache"); + var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField( + "updateTimestampsCache", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + updateTimestampsCacheField.SetValue(debugSessionFactory.ActualFactory, cache); //"Received" assertions can not be used since the collection is reused and cleared between calls.