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 3 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
2 changes: 2 additions & 0 deletions src/AsyncGenerator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
- name: GetFieldValue
- name: IsDBNull
- name: WriteLine
- name: AddAction
containingTypeName: ActionQueue
Copy link
Member

Choose a reason for hiding this comment

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

@maca88 we need a way to ignore a method if it's async counterpart is marked as obsolete. This would happen a lot in the future.

Copy link
Contributor

Choose a reason for hiding this comment

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

I will add an option to make this possible in the following days, most likely tomorrow.

Copy link
Member

@hazzik hazzik Dec 5, 2017

Choose a reason for hiding this comment

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

Thanks @maca88. Don't need to rush.

Copy link
Contributor

Choose a reason for hiding this comment

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

The new option was added and released with the version 0.7.0. To ignore all obsolete async counterparts add the following lines inside the analyzation property:

ignoreAsyncCounterparts:
- rule: Obsolete

where Obsolete is a method rule defined as:

- filters:
  - hasAttributeName: ObsoleteAttribute
  name: Obsolete

callForwarding: true
cancellationTokens:
guards: true
Expand Down
112 changes: 112 additions & 0 deletions src/NHibernate.Test/Async/SecondLevelCacheTest/InvalidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//------------------------------------------------------------------------------
// <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.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<UpdateTimestampsCache>(Sfi.Settings, new Dictionary<string, string>());
((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache);

var items = new List<Item>();
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<Item>(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<Item>(i));
await (session.DeleteAsync(item));
}
await (tx.CommitAsync());
}
}
//Should receive one preinvalidation and one invalidation per commit
await (cache.Received(3).PreInvalidateAsync(Arg.Is<object[]>(x => x.Length==1 && (string)x[0] == "Item"), CancellationToken.None));
await (cache.Received(3).InvalidateAsync(Arg.Is<object[]>(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();
}
}
}
91 changes: 91 additions & 0 deletions src/NHibernate.Test/SecondLevelCacheTest/InvalidationTests.cs
Original file line number Diff line number Diff line change
@@ -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<UpdateTimestampsCache>(Sfi.Settings, new Dictionary<string, string>());
((SessionFactoryImpl) (Sfi as DebugSessionFactory).ActualFactory).SetPropertyUsingReflection(x => x.UpdateTimestampsCache, cache);

var items = new List<Item>();
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<Item>(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<Item>(i);
session.Delete(item);
}
tx.Commit();
}
}
//Should receive one preinvalidation and one invalidation per commit
cache.Received(3).PreInvalidate(Arg.Is<object[]>(x => x.Length==1 && (string)x[0] == "Item"));
cache.Received(3).Invalidate(Arg.Is<object[]>(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();
}
}
}
32 changes: 32 additions & 0 deletions src/NHibernate.Test/TestExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<T, TProp>(this T instance, Expression<Func<T, TProp>> 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);
Copy link
Member

@fredericDelaporte fredericDelaporte Dec 7, 2017

Choose a reason for hiding this comment

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

I am not convinced by the need of SetPropertyUsingReflection in its current form. I would rather have just a SetField(object instance, string name) helper for ceasing re-coding the reflection. (Generic if you prefer, this may allow handling static fields too by passing a null object.)
Assuming camel case for the backing field, while we have more and more readonly auto-properties, camel case underscore names (which should be the default naming but due to Java porting we often have camel case instead), is not good in my opinion. We may try to be smarter and handle many conventions (as do the property accessor strategy in mappings), but I am not convinced it is worth it. And readonly auto-properties will remain hard to handle.

At the very least if we keep this, it should throw when not finding the field.

Copy link
Member Author

Choose a reason for hiding this comment

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

This was a method I just copied straight from my own at-work code, where we usually use it to set properties on NH mapped domain classes, where the setters are hidden. It uses the current form to provide chaining, so that settings many properties doesn't become a burden. The lambda is just refactoring friendly (we don't use the field version, it was added here)

I don't mind having a simpler implementation.

At the very least if we keep this, it should throw when not finding the field.

Absolutely. That was an omission.

Copy link
Member

Choose a reason for hiding this comment

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

So maybe adjust it by changing

if (field != null)
{
	field.SetValue(instance, value);
}

To:

if (field == null)
    var field = typeof(T).GetField("_" + name, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
    throw new InvalidOperationException($"Unable to find a setter or a backing field for property {method.Member.Name}");

field.SetValue(instance, value);

var field = typeof(T).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
{
field.SetValue(instance, value);
}
}
return instance;
}

}
}
8 changes: 4 additions & 4 deletions src/NHibernate/Async/Cache/UpdateTimestampsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -52,7 +52,7 @@ public async Task PreInvalidateAsync(object[] spaces, CancellationToken cancella

/// <summary></summary>
[MethodImpl()]
public async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken)
public virtual async Task InvalidateAsync(object[] spaces, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (await _invalidate.LockAsync())
Expand All @@ -69,7 +69,7 @@ public async Task InvalidateAsync(object[] spaces, CancellationToken cancellatio
}

[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 Expand Up @@ -108,4 +108,4 @@ public async Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp /* H
}
}
}
}
}
Loading