Skip to content

Reduce the number of calls to UpdateTimestampsCache #1467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/AsyncGenerator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
applyChanges: true
analyzation:
methodConversion:
- conversion: Ignore
hasAttributeName: ObsoleteAttribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for now we want to generate async counterparts for obsoleted methods (as they were here before the methods were marked obsolete)

- conversion: Ignore
name: PostProcessInsert
containingTypeName: HqlSqlWalker
Expand Down Expand Up @@ -96,6 +94,9 @@
- conversion: Ignore
name: Exists
containingTypeName: AbstractCollectionPersister
- conversion: Ignore
name: QuoteTableAndColumns
containingTypeName: SchemaMetadataUpdater
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was obsoleted in 5.0, and did not have an async counterpart, so ignore it explicitly.

- conversion: ToAsync
name: ExecuteReader
containingTypeName: IBatcher
Expand All @@ -119,6 +120,8 @@
- name: GetFieldValue
- name: IsDBNull
- name: WriteLine
ignoreAsyncCounterparts:
- rule: Obsolete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New from 0.7.0. From now on we want to generate async counterparts for obsolete members, but do not want to call them. Also, we would want to add obsolete members by hands where they ceased to be generated.

callForwarding: true
cancellationTokens:
guards: true
Expand Down Expand Up @@ -259,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
Expand Down
115 changes: 115 additions & 0 deletions src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


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;
using NUnit.Framework;

namespace NHibernate.Test.SecondLevelCacheTest
{
using System.Threading.Tasks;
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)
{
configuration.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName);
configuration.SetProperty(Environment.UseQueryCache, "true");
}

[Test]
public async Task InvalidatesEntitiesAsync()
{
var debugSessionFactory = (DebugSessionFactory) Sfi;

var cache = Substitute.For<UpdateTimestampsCache>(Sfi.Settings, new Dictionary<string, string>());

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.
//The received args are cloned and stored
var preInvalidations = new List<IReadOnlyCollection<string>>();
var invalidations = new List<IReadOnlyCollection<string>>();

await (cache.PreInvalidateAsync(Arg.Do<IReadOnlyCollection<string>>(x => preInvalidations.Add(x.ToList())), CancellationToken.None));
await (cache.InvalidateAsync(Arg.Do<IReadOnlyCollection<string>>(x => invalidations.Add(x.ToList())), CancellationToken.None));

using (var session = OpenSession())
{
using (var 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 (var tx = session.BeginTransaction())
{
foreach (var i in Enumerable.Range(1, 10))
{
var item = await (session.GetAsync<Item>(i));
item.Name = item.Id.ToString();
}

await (tx.CommitAsync());
}

using (var tx = session.BeginTransaction())
{
foreach (var i in Enumerable.Range(1, 10))
{
var item = await (session.GetAsync<Item>(i));
await (session.DeleteAsync(item));
}

await (tx.CommitAsync());
}
}

//Should receive one preinvalidation and one invalidation per commit
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"));
}

protected override void OnTearDown()
{
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
s.Delete("from Item");
tx.Commit();
}
}
}
}
102 changes: 102 additions & 0 deletions src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NHibernate.Cache;
using NHibernate.Cfg;
using NHibernate.Impl;
using NHibernate.Test.SecondLevelCacheTests;
using NSubstitute;
using NUnit.Framework;

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)
{
configuration.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName);
configuration.SetProperty(Environment.UseQueryCache, "true");
}

[Test]
public void InvalidatesEntities()
{
var debugSessionFactory = (DebugSessionFactory) Sfi;

var cache = Substitute.For<UpdateTimestampsCache>(Sfi.Settings, new Dictionary<string, string>());

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.
//The received args are cloned and stored
var preInvalidations = new List<IReadOnlyCollection<string>>();
var invalidations = new List<IReadOnlyCollection<string>>();

cache.PreInvalidate(Arg.Do<IReadOnlyCollection<string>>(x => preInvalidations.Add(x.ToList())));
cache.Invalidate(Arg.Do<IReadOnlyCollection<string>>(x => invalidations.Add(x.ToList())));

using (var session = OpenSession())
{
using (var tx = session.BeginTransaction())
{
foreach (var i in Enumerable.Range(1, 10))
{
var item = new Item {Id = i};
session.Save(item);
}

tx.Commit();
}

using (var tx = session.BeginTransaction())
{
foreach (var i in Enumerable.Range(1, 10))
{
var item = session.Get<Item>(i);
item.Name = item.Id.ToString();
}

tx.Commit();
}

using (var tx = session.BeginTransaction())
{
foreach (var i in Enumerable.Range(1, 10))
{
var item = session.Get<Item>(i);
session.Delete(item);
}

tx.Commit();
}
}

//Should receive one preinvalidation and one invalidation per commit
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"));
}

protected override void OnTearDown()
{
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
s.Delete("from Item");
tx.Commit();
}
}
}
}
60 changes: 50 additions & 10 deletions src/NHibernate/Async/Cache/UpdateTimestampsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

using NHibernate.Cfg;
Expand All @@ -24,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)
{
Expand All @@ -33,43 +34,82 @@ public Task ClearAsync(CancellationToken cancellationToken)
return updateTimestamps.ClearAsync(cancellationToken);
}

//Since v5.1
[Obsolete("Please use PreInvalidate(IReadOnlyCollection<string>) instead.")]
public Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<object>(cancellationToken);
}
try
{
//Only for backwards compatibility.
return PreInvalidateAsync(spaces.OfType<string>().ToList(), cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<object>(ex);
}
}

[MethodImpl()]
public async Task PreInvalidateAsync(object[] spaces, CancellationToken cancellationToken)
public virtual async Task PreInvalidateAsync(IReadOnlyCollection<string> 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);
}

/// <summary></summary>
//Since v5.1
[Obsolete("Please use PreInvalidate(IReadOnlyCollection<string>) instead.")]
public Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<object>(cancellationToken);
}
try
{
//Only for backwards compatibility.
return InvalidateAsync(spaces.OfType<string>().ToList(), cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<object>(ex);
}
}

[MethodImpl()]
public async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken)
public virtual async Task InvalidateAsync(IReadOnlyCollection<string> spaces, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (await _invalidate.LockAsync())
{
//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("Invalidating space [{0}]", spaces[i]);
await (updateTimestamps.PutAsync(spaces[i], ts, cancellationToken)).ConfigureAwait(false);
log.Debug("Invalidating space [{0}]", space);
await (updateTimestamps.PutAsync(space, ts, cancellationToken)).ConfigureAwait(false);
}
}
}

[MethodImpl()]
public async Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken)
public virtual async Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp /* H2.1 has Long here */, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (await _isUpToDate.LockAsync())
Expand Down
Loading