Skip to content

Commit 1b5fce7

Browse files
committed
Altered handling of cache invalidation
1 parent 338a827 commit 1b5fce7

File tree

10 files changed

+281
-43
lines changed

10 files changed

+281
-43
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 item in items)
63+
{
64+
item.Name = item.Id.ToString();
65+
}
66+
await (tx.CommitAsync());
67+
}
68+
69+
using (ITransaction tx = session.BeginTransaction())
70+
{
71+
foreach (var item in items)
72+
{
73+
await (session.DeleteAsync(item));
74+
}
75+
await (tx.CommitAsync());
76+
}
77+
}
78+
//Should receive one preinvalidation and one invalidation per commit
79+
await (cache.Received(3).PreInvalidateAsync(Arg.Any<object[]>(), CancellationToken.None));
80+
await (cache.Received(3).InvalidateAsync(Arg.Any<object[]>(), CancellationToken.None));
81+
}
82+
83+
public async Task CleanUpAsync(CancellationToken cancellationToken = default(CancellationToken))
84+
{
85+
using (ISession s = OpenSession())
86+
using (ITransaction tx = s.BeginTransaction())
87+
{
88+
await (s.DeleteAsync("from Item", cancellationToken));
89+
await (tx.CommitAsync(cancellationToken));
90+
}
91+
}
92+
93+
public void CleanUp()
94+
{
95+
using (ISession s = OpenSession())
96+
using (ITransaction tx = s.BeginTransaction())
97+
{
98+
s.Delete("from Item");
99+
tx.Commit();
100+
}
101+
}
102+
103+
protected override void OnTearDown()
104+
{
105+
CleanUp();
106+
base.OnTearDown();
107+
}
108+
}
109+
}

src/NHibernate.Test/DebugSessionFactory.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ public partial class DebugSessionFactory : ISessionFactoryImplementor
4040
public DebugConnectionProvider DebugConnectionProvider { get; }
4141
public ISessionFactoryImplementor ActualFactory { get; }
4242

43-
public EventListeners EventListeners => ((SessionFactoryImpl)ActualFactory).EventListeners;
43+
public EventListeners EventListeners => ((SessionFactoryImpl) ActualFactory).EventListeners;
4444

4545
private readonly ConcurrentBag<ISessionImplementor> _openedSessions = new ConcurrentBag<ISessionImplementor>();
4646
private static readonly ILog _log = LogManager.GetLogger(typeof(DebugSessionFactory).Assembly, typeof(TestCase));
47+
private UpdateTimestampsCache _updateTimestampsCache;
4748

4849
public DebugSessionFactory(ISessionFactory actualFactory)
4950
{
50-
ActualFactory = (ISessionFactoryImplementor)actualFactory;
51+
ActualFactory = (ISessionFactoryImplementor) actualFactory;
5152
DebugConnectionProvider = ActualFactory.ConnectionProvider as DebugConnectionProvider;
5253
}
5354

@@ -280,7 +281,11 @@ ISession ISessionFactory.GetCurrentSession()
280281

281282
ITransactionFactory ISessionFactoryImplementor.TransactionFactory => ActualFactory.TransactionFactory;
282283

283-
UpdateTimestampsCache ISessionFactoryImplementor.UpdateTimestampsCache => ActualFactory.UpdateTimestampsCache;
284+
public UpdateTimestampsCache UpdateTimestampsCache
285+
{
286+
get => _updateTimestampsCache ?? ActualFactory.UpdateTimestampsCache;
287+
set { _updateTimestampsCache = value; }
288+
}
284289

285290
IStatisticsImplementor ISessionFactoryImplementor.StatisticsImplementor => ActualFactory.StatisticsImplementor;
286291

@@ -381,20 +386,20 @@ string ISessionFactoryImplementor.TryGetGuessEntityName(System.Type implementor)
381386
public static ISessionCreationOptions GetCreationOptions<T>(ISessionBuilder<T> sessionBuilder) where T : ISessionBuilder<T>
382387
{
383388
return (sessionBuilder as SessionBuilder)?.CreationOptions ??
384-
(ISessionCreationOptions)sessionBuilder;
389+
(ISessionCreationOptions) sessionBuilder;
385390
}
386391

387392
public static ISessionCreationOptions GetCreationOptions(IStatelessSessionBuilder sessionBuilder)
388393
{
389-
return ((StatelessSessionBuilder)sessionBuilder).CreationOptions;
394+
return ((StatelessSessionBuilder) sessionBuilder).CreationOptions;
390395
}
391396

392397
internal class SessionBuilder : ISessionBuilder
393398
{
394399
private readonly ISessionBuilder _actualBuilder;
395400
private readonly DebugSessionFactory _debugFactory;
396401

397-
internal ISessionCreationOptions CreationOptions => (ISessionCreationOptions)_actualBuilder;
402+
internal ISessionCreationOptions CreationOptions => (ISessionCreationOptions) _actualBuilder;
398403

399404
public SessionBuilder(ISessionBuilder actualBuilder, DebugSessionFactory debugFactory)
400405
{
@@ -461,7 +466,7 @@ internal class StatelessSessionBuilder : IStatelessSessionBuilder
461466
private readonly IStatelessSessionBuilder _actualBuilder;
462467
private readonly DebugSessionFactory _debugFactory;
463468

464-
internal ISessionCreationOptions CreationOptions => (ISessionCreationOptions)_actualBuilder;
469+
internal ISessionCreationOptions CreationOptions => (ISessionCreationOptions) _actualBuilder;
465470

466471
public StatelessSessionBuilder(IStatelessSessionBuilder actualBuilder, DebugSessionFactory debugFactory)
467472
{
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 item in items)
52+
{
53+
item.Name = item.Id.ToString();
54+
}
55+
tx.Commit();
56+
}
57+
58+
using (ITransaction tx = session.BeginTransaction())
59+
{
60+
foreach (var item in items)
61+
{
62+
session.Delete(item);
63+
}
64+
tx.Commit();
65+
}
66+
}
67+
//Should receive one preinvalidation and one invalidation per commit
68+
cache.Received(3).PreInvalidate(Arg.Any<object[]>());
69+
cache.Received(3).Invalidate(Arg.Any<object[]>());
70+
}
71+
72+
public void CleanUp()
73+
{
74+
using (ISession s = OpenSession())
75+
using (ITransaction tx = s.BeginTransaction())
76+
{
77+
s.Delete("from Item");
78+
tx.Commit();
79+
}
80+
}
81+
82+
protected override void OnTearDown()
83+
{
84+
CleanUp();
85+
base.OnTearDown();
86+
}
87+
}
88+
}

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: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,6 @@ namespace NHibernate.Engine
2424
using System.Threading;
2525
public partial class ActionQueue
2626
{
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-
}
3627

3728
private async Task ExecuteActionsAsync(IList list, CancellationToken cancellationToken)
3829
{
@@ -57,22 +48,9 @@ public async Task ExecuteAsync(IExecutable executable, CancellationToken cancell
5748
}
5849
finally
5950
{
60-
await (RegisterCleanupActionsAsync(executable, cancellationToken)).ConfigureAwait(false);
51+
RegisterCleanupActions(executable);
6152
}
6253
}
63-
64-
private async Task RegisterCleanupActionsAsync(IExecutable executable, CancellationToken cancellationToken)
65-
{
66-
cancellationToken.ThrowIfCancellationRequested();
67-
beforeTransactionProcesses.Register(executable.BeforeTransactionCompletionProcess);
68-
if (session.Factory.Settings.IsQueryCacheEnabled)
69-
{
70-
string[] spaces = executable.PropertySpaces;
71-
afterTransactionProcesses.AddSpacesToInvalidate(spaces);
72-
await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(spaces, cancellationToken)).ConfigureAwait(false);
73-
}
74-
afterTransactionProcesses.Register(executable.AfterTransactionCompletionProcess);
75-
}
7654

7755
/// <summary>
7856
/// Perform all currently queued entity-insertion actions.
@@ -94,6 +72,10 @@ public Task ExecuteInsertsAsync(CancellationToken cancellationToken)
9472
public async Task ExecuteActionsAsync(CancellationToken cancellationToken)
9573
{
9674
cancellationToken.ThrowIfCancellationRequested();
75+
if (session.Factory.Settings.IsQueryCacheEnabled)
76+
{
77+
await (session.Factory.UpdateTimestampsCache.PreInvalidateAsync(querySpaces.ToArray(), cancellationToken)).ConfigureAwait(false);
78+
}
9779
await (ExecuteActionsAsync(insertions, cancellationToken)).ConfigureAwait(false);
9880
await (ExecuteActionsAsync(updates, cancellationToken)).ConfigureAwait(false);
9981
await (ExecuteActionsAsync(collectionRemovals, cancellationToken)).ConfigureAwait(false);

src/NHibernate/Async/Engine/Query/NativeSQLQueryPlan.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private async Task CoordinateSharedCacheCleanupAsync(ISessionImplementor session
4343

4444
if (session.IsEventSource)
4545
{
46-
await (((IEventSource)session).ActionQueue.AddActionAsync(action, cancellationToken)).ConfigureAwait(false);
46+
((IEventSource)session).ActionQueue.AddAction(action);
4747
}
4848
}
4949

src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ protected virtual async Task CoordinateSharedCacheCleanupAsync(ISessionImplement
4343

4444
if (session.IsEventSource)
4545
{
46-
await (((IEventSource)session).ActionQueue.AddActionAsync(action, cancellationToken)).ConfigureAwait(false);
46+
((IEventSource)session).ActionQueue.AddAction(action);
4747
}
4848
}
4949

0 commit comments

Comments
 (0)