Skip to content

Commit 391d231

Browse files
committed
Batching and distincting cache invalidation calls
1 parent 338a827 commit 391d231

File tree

9 files changed

+349
-55
lines changed

9 files changed

+349
-55
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Collections;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using System.Text;
15+
using System.Threading.Tasks;
16+
using NHibernate.Cache;
17+
using NHibernate.Cfg;
18+
using NHibernate.Impl;
19+
using NHibernate.Test.SecondLevelCacheTests;
20+
using NSubstitute;
21+
using NUnit.Framework;
22+
using Environment = System.Environment;
23+
24+
namespace NHibernate.Test.SecondLevelCacheTest
25+
{
26+
using System.Threading;
27+
[TestFixture]
28+
public class InvalidationTestsAsync : TestCase
29+
{
30+
protected override string MappingsAssembly => "NHibernate.Test";
31+
32+
protected override IList Mappings => new[] { "SecondLevelCacheTest.Item.hbm.xml" };
33+
34+
protected override void Configure(Configuration configuration)
35+
{
36+
base.Configure(configuration);
37+
configuration.Properties[Cfg.Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName;
38+
configuration.Properties[Cfg.Environment.UseQueryCache] = "true";
39+
}
40+
41+
[Test]
42+
public async Task InvalidatesEntitiesAsync()
43+
{
44+
var cache = Substitute.For<UpdateTimestampsCache>(Sfi.Settings, new Dictionary<string, string>());
45+
((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache);
46+
47+
var items = new List<Item>();
48+
using (ISession session = OpenSession())
49+
{
50+
using (ITransaction tx = session.BeginTransaction())
51+
{
52+
foreach (var i in Enumerable.Range(1, 10))
53+
{
54+
var item = new Item { Id = i };
55+
await (session.SaveAsync(item));
56+
}
57+
await (tx.CommitAsync());
58+
}
59+
60+
using (ITransaction tx = session.BeginTransaction())
61+
{
62+
foreach (var i in Enumerable.Range(1, 10))
63+
{
64+
var item = await (session.GetAsync<Item>(i));
65+
item.Name = item.Id.ToString();
66+
}
67+
await (tx.CommitAsync());
68+
}
69+
70+
using (ITransaction tx = session.BeginTransaction())
71+
{
72+
foreach (var i in Enumerable.Range(1, 10))
73+
{
74+
var item = await (session.GetAsync<Item>(i));
75+
await (session.DeleteAsync(item));
76+
}
77+
await (tx.CommitAsync());
78+
}
79+
}
80+
//Should receive one preinvalidation and one invalidation per commit
81+
await (cache.Received(3).PreInvalidateAsync(Arg.Is<object[]>(x => x.Length==1 && (string)x[0] == "Item"), CancellationToken.None));
82+
await (cache.Received(3).InvalidateAsync(Arg.Is<object[]>(x => x.Length == 1 && (string) x[0] == "Item"), CancellationToken.None));
83+
84+
}
85+
86+
public async Task CleanUpAsync(CancellationToken cancellationToken = default(CancellationToken))
87+
{
88+
using (ISession s = OpenSession())
89+
using (ITransaction tx = s.BeginTransaction())
90+
{
91+
await (s.DeleteAsync("from Item", cancellationToken));
92+
await (tx.CommitAsync(cancellationToken));
93+
}
94+
}
95+
96+
public void CleanUp()
97+
{
98+
using (ISession s = OpenSession())
99+
using (ITransaction tx = s.BeginTransaction())
100+
{
101+
s.Delete("from Item");
102+
tx.Commit();
103+
}
104+
}
105+
106+
protected override void OnTearDown()
107+
{
108+
CleanUp();
109+
base.OnTearDown();
110+
}
111+
}
112+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using NHibernate.Cache;
7+
using NHibernate.Cfg;
8+
using NHibernate.Impl;
9+
using NHibernate.Test.SecondLevelCacheTests;
10+
using NSubstitute;
11+
using NUnit.Framework;
12+
using Environment = System.Environment;
13+
14+
namespace NHibernate.Test.SecondLevelCacheTest
15+
{
16+
[TestFixture]
17+
public class InvalidationTests : TestCase
18+
{
19+
protected override string MappingsAssembly => "NHibernate.Test";
20+
21+
protected override IList Mappings => new[] { "SecondLevelCacheTest.Item.hbm.xml" };
22+
23+
protected override void Configure(Configuration configuration)
24+
{
25+
base.Configure(configuration);
26+
configuration.Properties[Cfg.Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName;
27+
configuration.Properties[Cfg.Environment.UseQueryCache] = "true";
28+
}
29+
30+
[Test]
31+
public void InvalidatesEntities()
32+
{
33+
var cache = Substitute.For<UpdateTimestampsCache>(Sfi.Settings, new Dictionary<string, string>());
34+
((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache);
35+
36+
var items = new List<Item>();
37+
using (ISession session = OpenSession())
38+
{
39+
using (ITransaction tx = session.BeginTransaction())
40+
{
41+
foreach (var i in Enumerable.Range(1, 10))
42+
{
43+
var item = new Item { Id = i };
44+
session.Save(item);
45+
}
46+
tx.Commit();
47+
}
48+
49+
using (ITransaction tx = session.BeginTransaction())
50+
{
51+
foreach (var i in Enumerable.Range(1, 10))
52+
{
53+
var item = session.Get<Item>(i);
54+
item.Name = item.Id.ToString();
55+
}
56+
tx.Commit();
57+
}
58+
59+
using (ITransaction tx = session.BeginTransaction())
60+
{
61+
foreach (var i in Enumerable.Range(1, 10))
62+
{
63+
var item = session.Get<Item>(i);
64+
session.Delete(item);
65+
}
66+
tx.Commit();
67+
}
68+
}
69+
//Should receive one preinvalidation and one invalidation per commit
70+
cache.Received(3).PreInvalidate(Arg.Is<object[]>(x => x.Length==1 && (string)x[0] == "Item"));
71+
cache.Received(3).Invalidate(Arg.Is<object[]>(x => x.Length == 1 && (string) x[0] == "Item"));
72+
73+
}
74+
75+
public void CleanUp()
76+
{
77+
using (ISession s = OpenSession())
78+
using (ITransaction tx = s.BeginTransaction())
79+
{
80+
s.Delete("from Item");
81+
tx.Commit();
82+
}
83+
}
84+
85+
protected override void OnTearDown()
86+
{
87+
CleanUp();
88+
base.OnTearDown();
89+
}
90+
}
91+
}

src/NHibernate.Test/TestExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
5+
namespace NHibernate.Test
6+
{
7+
//Utility extension methods for use in unit tests.
8+
public static class TestExtensions
9+
{
10+
public static T SetPropertyUsingReflection<T, TProp>(this T instance, Expression<Func<T, TProp>> property, TProp value)
11+
{
12+
var method = property.Body as MemberExpression;
13+
var propertyInfo = typeof(T).GetProperty(method.Member.Name);
14+
if (propertyInfo!= null && propertyInfo.CanWrite)
15+
{
16+
propertyInfo.SetValue(instance, value);
17+
}
18+
else
19+
{
20+
//camel cased field
21+
var name = method.Member.Name.Substring(0, 1).ToLowerInvariant() + method.Member.Name.Substring(1);
22+
var field = typeof(T).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
23+
if (field != null)
24+
{
25+
field.SetValue(instance, value);
26+
}
27+
}
28+
return instance;
29+
}
30+
31+
}
32+
}

src/NHibernate/Async/Cache/UpdateTimestampsCache.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public Task ClearAsync(CancellationToken cancellationToken)
3434
}
3535

3636
[MethodImpl()]
37-
public async Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken)
37+
public virtual async Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken)
3838
{
3939
cancellationToken.ThrowIfCancellationRequested();
4040
using (await _preInvalidate.LockAsync())
@@ -52,7 +52,7 @@ public async Task PreInvalidateAsync(object[] spaces, CancellationToken cancella
5252

5353
/// <summary></summary>
5454
[MethodImpl()]
55-
public async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken)
55+
public virtual async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken)
5656
{
5757
cancellationToken.ThrowIfCancellationRequested();
5858
using (await _invalidate.LockAsync())
@@ -69,7 +69,7 @@ public async Task InvalidateAsync(object[] spaces, CancellationToken cancellatio
6969
}
7070

7171
[MethodImpl()]
72-
public async Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken)
72+
public virtual async Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken)
7373
{
7474
cancellationToken.ThrowIfCancellationRequested();
7575
using (await _isUpToDate.LockAsync())
@@ -108,4 +108,4 @@ public async Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp /* H
108108
}
109109
}
110110
}
111-
}
111+
}

src/NHibernate/Async/Engine/ActionQueue.cs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,16 @@
1313
using System.Collections.Generic;
1414
using System.Linq;
1515
using System.Text;
16-
16+
using System.Threading;
17+
using System.Threading.Tasks;
1718
using NHibernate.Action;
1819
using NHibernate.Cache;
1920
using NHibernate.Type;
2021

2122
namespace NHibernate.Engine
2223
{
23-
using System.Threading.Tasks;
24-
using System.Threading;
2524
public partial class ActionQueue
2625
{
27-
28-
public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, CancellationToken cancellationToken)
29-
{
30-
if (cancellationToken.IsCancellationRequested)
31-
{
32-
return Task.FromCanceled<object>(cancellationToken);
33-
}
34-
return RegisterCleanupActionsAsync(cleanupAction, cancellationToken);
35-
}
3626

3727
private async Task ExecuteActionsAsync(IList list, CancellationToken cancellationToken)
3828
{
@@ -41,37 +31,54 @@ private async Task ExecuteActionsAsync(IList list, CancellationToken cancellatio
4131
// It will then fail here due to list being modified. (Some previous code was dodging the
4232
// trouble with a for loop which was not failing provided the list was not getting smaller.
4333
// But then it was clearing it without having executed added actions (if any), ...)
34+
4435
foreach (IExecutable executable in list)
45-
await (ExecuteAsync(executable, cancellationToken)).ConfigureAwait(false);
46-
36+
{
37+
await (InnerExecuteAsync(executable, cancellationToken)).ConfigureAwait(false);
38+
}
4739
list.Clear();
4840
await (session.Batcher.ExecuteBatchAsync(cancellationToken)).ConfigureAwait(false);
41+
42+
}
43+
44+
private async Task AfterExecutionsAsync(CancellationToken cancellationToken)
45+
{
46+
cancellationToken.ThrowIfCancellationRequested();
47+
if (session.Factory.Settings.IsQueryCacheEnabled)
48+
{
49+
await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(executedSpaces.ToArray(), cancellationToken)).ConfigureAwait(false);
50+
}
51+
executedSpaces.Clear();
4952
}
5053

5154
public async Task ExecuteAsync(IExecutable executable, CancellationToken cancellationToken)
5255
{
5356
cancellationToken.ThrowIfCancellationRequested();
5457
try
5558
{
56-
await (executable.ExecuteAsync(cancellationToken)).ConfigureAwait(false);
59+
await (InnerExecuteAsync(executable, cancellationToken)).ConfigureAwait(false);
5760
}
5861
finally
5962
{
60-
await (RegisterCleanupActionsAsync(executable, cancellationToken)).ConfigureAwait(false);
63+
await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false);
6164
}
6265
}
63-
64-
private async Task RegisterCleanupActionsAsync(IExecutable executable, CancellationToken cancellationToken)
66+
67+
private async Task InnerExecuteAsync(IExecutable executable, CancellationToken cancellationToken)
6568
{
6669
cancellationToken.ThrowIfCancellationRequested();
67-
beforeTransactionProcesses.Register(executable.BeforeTransactionCompletionProcess);
68-
if (session.Factory.Settings.IsQueryCacheEnabled)
70+
if (executable.PropertySpaces != null)
6971
{
70-
string[] spaces = executable.PropertySpaces;
71-
afterTransactionProcesses.AddSpacesToInvalidate(spaces);
72-
await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(spaces, cancellationToken)).ConfigureAwait(false);
72+
executedSpaces.UnionWith(executable.PropertySpaces);
73+
}
74+
try
75+
{
76+
await (executable.ExecuteAsync(cancellationToken)).ConfigureAwait(false);
77+
}
78+
finally
79+
{
80+
RegisterCleanupActions(executable);
7381
}
74-
afterTransactionProcesses.Register(executable.AfterTransactionCompletionProcess);
7582
}
7683

7784
/// <summary>
@@ -94,12 +101,19 @@ public Task ExecuteInsertsAsync(CancellationToken cancellationToken)
94101
public async Task ExecuteActionsAsync(CancellationToken cancellationToken)
95102
{
96103
cancellationToken.ThrowIfCancellationRequested();
97-
await (ExecuteActionsAsync(insertions, cancellationToken)).ConfigureAwait(false);
98-
await (ExecuteActionsAsync(updates, cancellationToken)).ConfigureAwait(false);
99-
await (ExecuteActionsAsync(collectionRemovals, cancellationToken)).ConfigureAwait(false);
100-
await (ExecuteActionsAsync(collectionUpdates, cancellationToken)).ConfigureAwait(false);
101-
await (ExecuteActionsAsync(collectionCreations, cancellationToken)).ConfigureAwait(false);
102-
await (ExecuteActionsAsync(deletions, cancellationToken)).ConfigureAwait(false);
104+
try
105+
{
106+
await (ExecuteActionsAsync(insertions, cancellationToken)).ConfigureAwait(false);
107+
await (ExecuteActionsAsync(updates, cancellationToken)).ConfigureAwait(false);
108+
await (ExecuteActionsAsync(collectionRemovals, cancellationToken)).ConfigureAwait(false);
109+
await (ExecuteActionsAsync(collectionUpdates, cancellationToken)).ConfigureAwait(false);
110+
await (ExecuteActionsAsync(collectionCreations, cancellationToken)).ConfigureAwait(false);
111+
await (ExecuteActionsAsync(deletions, cancellationToken)).ConfigureAwait(false);
112+
}
113+
finally
114+
{
115+
await (AfterExecutionsAsync(cancellationToken)).ConfigureAwait(false);
116+
}
103117
}
104118

105119
private async Task PrepareActionsAsync(IList queue, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)