-
Notifications
You must be signed in to change notification settings - Fork 934
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just comments on the code style. I did not review the actual changes in PR.
public UpdateTimestampsCache UpdateTimestampsCache | ||
{ | ||
get => _updateTimestampsCache ?? ActualFactory.UpdateTimestampsCache; | ||
set { _updateTimestampsCache = value; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use consistent code style. Either both need to be in brackets or to be lambdas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
@@ -24,15 +24,6 @@ namespace NHibernate.Engine | |||
using System.Threading; | |||
public partial class ActionQueue | |||
{ | |||
|
|||
public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, CancellationToken cancellationToken) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please manually add back this method (into non-async part) as an async-to-sync wrapper to AddAction. And make it obsolete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It happened automatically, but I'll try what you said.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, making it obsolete didn't stop the async generator from generating calls to it, and since that is treated as an error, it prevents compilation. I fixed it manually, for the sake of the review, but is there a way to prevent this?
The whitespace changes I have seen match editorconfig, they are "good" ones. But for easing reviews, global reformatting of files should be avoided. These changes normally do not occurs without an explicit call to a global reformatting action. (That annoys me to not be able to fire a global reformat when I want to reformat the code I have just added, but well, global reformat annoys reviewers instead.) |
src/NHibernate/Engine/ActionQueue.cs
Outdated
@@ -169,6 +187,10 @@ public void ExecuteInserts() | |||
/// </summary> | |||
public void ExecuteActions() | |||
{ | |||
if (session.Factory.Settings.IsQueryCacheEnabled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExecuteInserts
(a few lines above) should call this as well.- It seems that it shall be called after execute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also Execute(action)
can be called directly. Which then will not "pre-invalidate" the cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bummer. I had made a mental note of it, but forgot.
src/NHibernate/Engine/ActionQueue.cs
Outdated
@@ -56,6 +57,8 @@ public ActionQueue(ISessionImplementor session) | |||
|
|||
afterTransactionProcesses = new AfterTransactionCompletionProcessQueue(session); | |||
beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue(session); | |||
|
|||
querySpaces = new HashSet<string>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to utilize afterTransactionProcesses.querySpacesToInvalidate
instead of creating an additional hashset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or opposite.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or opposite. As you have 2 collections which hold exactly the same data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you've forgot to clear the field.
1b5fce7
to
391d231
Compare
@@ -42,6 +43,7 @@ public partial class ActionQueue | |||
|
|||
private readonly AfterTransactionCompletionProcessQueue afterTransactionProcesses; | |||
private readonly BeforeTransactionCompletionProcessQueue beforeTransactionProcesses; | |||
private readonly HashSet<string> executedSpaces; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gliljas I'm still not sure why you need an additional collection for executedSpaces
if you can use AfterTransactionCompletionProcessQueue.querySpacesToInvalidate
(just make it internal) which holds exactly the same data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hazzik I think that it's a bit wrong to to piggyback on a class that is not involved in the PreInvalidation. However, I have now moved them together, so that the spaces added to
AfterTransactionCompletionProcessQueue
comes from the same collection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don’t understand why you can not use the same collection
src/NHibernate/Engine/ActionQueue.cs
Outdated
if (session.Factory.Settings.IsQueryCacheEnabled) | ||
{ | ||
var spaces = executedSpaces.ToArray(); | ||
afterTransactionProcesses.AddSpacesToInvalidate(spaces); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make the parameter of AddSpacesToInvalidate
IReadOnlyCollection<string>
to avoid calling .ToArray()
.
Or, actually, remove that method and use executedSpaces
directly instead of querySpacesToInvalidate
collection.
@gliljas I'll make a PR into your branch. It is apparently easier to code than explain:) |
@hazzik Console.WriteLine("Please do") |
src/AsyncGenerator.yml
Outdated
@@ -119,6 +119,8 @@ | |||
- name: GetFieldValue | |||
- name: IsDBNull | |||
- name: WriteLine | |||
- name: AddAction | |||
containingTypeName: ActionQueue |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Move invalidation of caches to ActionQueue
There is a bug in NSubstitute, which they seem do not want to acknowledge: nsubstitute/NSubstitute#70. I'll make a necessary workaround. |
Fixed assertions in unit test.
@gliljas haha, I've made almost the same adjustments. Need to regenerate the tests thou. |
@hazzik Ah, crap. Will do. |
|
||
} | ||
|
||
private bool IsRight(HashSet<string> x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm...added when debugging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
@@ -4,8 +4,6 @@ | |||
applyChanges: true | |||
analyzation: | |||
methodConversion: | |||
- conversion: Ignore | |||
hasAttributeName: ObsoleteAttribute |
There was a problem hiding this comment.
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)
@@ -96,6 +94,9 @@ | |||
- conversion: Ignore | |||
name: Exists | |||
containingTypeName: AbstractCollectionPersister | |||
- conversion: Ignore | |||
name: QuoteTableAndColumns | |||
containingTypeName: SchemaMetadataUpdater |
There was a problem hiding this comment.
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.
@@ -119,6 +120,8 @@ | |||
- name: GetFieldValue | |||
- name: IsDBNull | |||
- name: WriteLine | |||
ignoreAsyncCounterparts: | |||
- rule: Obsolete |
There was a problem hiding this comment.
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.
I have relaunched the failed builds, they have got a network connectivity issue. |
Thanks @fredericDelaporte I think the title of the PR needs to be changed to something more descriptive |
else | ||
{ | ||
//camel cased field | ||
var name = method.Member.Name.Substring(0, 1).ToLowerInvariant() + method.Member.Name.Substring(1); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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);
} | ||
|
||
[MethodImpl(MethodImplOptions.Synchronized)] | ||
public virtual void PreInvalidate(IReadOnlyCollection<string> spaces) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think this class is meant to be extended. (It should likely be sealed, but it could be done only for 6.0.) Is virtual
necessary for NSubstitute? If yes I quite dislike to have to do that for testing purpose.
Otherwise why should it be virtual
?
(Same for the two next virtual
methods.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm more in the "virtual should be the default" camp, but yes, it's for NSubstitute. It's very hard to create real unit tests in NHibernate, since everything is so coupled. I figured making these methods virtual was a small price to pay for coming a bit closer to a unit test. An alternative (which I explored) is to create mocks of the underlying ICache, but that's actually quite hard, since the provider is instantiated using reflection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, not as big as making something more visible just for test. So ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, not as big as making something more visible just for test. So ok.
@fredericDelaporte I was unsuccessfully trying to comprehend your last comment. Could you please explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switching something to virtual just for tests is not, in most cases, a big concern. (At least, far from being as big as some other "just for tests" changes we have already seen in NHibernate, and which were raising the visibility of some members.)
So let it be for this case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the fixes, @hazzik
Removed TestExtensions and made UpdateTimestampsCache.Clear/ClearAsync virtual. |
This needs a bit of review, since it maybe just goes from wrong to less wrong. The problem is that 2nd level UpdateTimestampCache invalidation is performed twice per action. Insert 200 items, the cache is invalidated 400 times. Unnecessary for "cheap" caches, a significant performance hit for caches with latency.
This PR reduces the number from 400 to 2. It may, due to its design, perform it even on things that fail to execute, but that only has unnecessary invalidation as a side effect.
I see now that a bit of whitespace has been added (probably by R#). The wrong whitespaces?