Skip to content

NH-4077 - do not auto-flush while already flushing #682

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

Conversation

fredericDelaporte
Copy link
Member

NH-4077.

The trouble was due to auto-flushing getting triggered from a flush event, altering the flush action list while it was iterating over it.

/// being called multiple times.
/// </summary>
/// <returns>A disposable to dispose when auto flush should be restored.</returns>
IDisposable SuspendAutoFlush();
Copy link
Member Author

Choose a reason for hiding this comment

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

Needing it from an other internal class than the session, and factoring it by the way.

finally
{
AfterOperation(success);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as previous AfterOperation case.

finally
{
AfterOperation(success);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Discrepancy: the AfterOperation(success); was previously put after decrementing back dontFlushFromFind, meaning after operations could trigger auto-flush. Now it is done before leaving the new using, so before re-enabling auto-flushes.
But AfterOperation does in fact nothing if we are inside a transaction. And when we are not inside a transaction, auto-flush is anyway already disabled. So this change has no consequences with current code.

finally
{
dontFlushFromFind--;
AfterOperation(success);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as previous AfterOperation case.

finally
{
AfterOperation(success);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as previous AfterOperation case.

@hazzik hazzik added the t: Bug label Sep 5, 2017
Copy link
Contributor

@BhaaLseN BhaaLseN left a comment

Choose a reason for hiding this comment

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

I backported the changes to 4.1.1.GA and it worked just fine with our old bugged code; so I believe the changes are good...at least for what we use it for.

/// <inheritdoc/>
public IDisposable SuspendAutoFlush()
{
_suspendAutoFlushCount++;
Copy link
Contributor

Choose a reason for hiding this comment

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

Any thoughts on putting this in the SuspendAutoFlushHelper ctor? IMO it makes it more obvious that the operation is (supposed to be) symmetric, plus you cannot forget one half if you need to reuse that class elsewhere (although unlikely, given it's private)

Also, thoughts on using Interlocked.Increment instead for atomicity?

Copy link
Member Author

@fredericDelaporte fredericDelaporte Sep 5, 2017

Choose a reason for hiding this comment

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

Make sens moving to constructor. But as sessions are inherently non-thread safe and so are supposed to be used only by one thread at a time, I will not introduce a thread safety guard here.

@fredericDelaporte fredericDelaporte force-pushed the NH-4077-Master branch 2 times, most recently from 820b080 to b4eaab9 Compare September 5, 2017 09:24
@hazzik
Copy link
Member

hazzik commented Sep 5, 2017

I think it would make sense to move SuspendAutoFlush to IEventSource interface and the implementation into SessionImpl. As none of these matters for stateless sessions

@fredericDelaporte
Copy link
Member Author

So, moving by the way the AutoFlushSuspended property (previously named DontFlushFromFind) too.

@hazzik
Copy link
Member

hazzik commented Sep 5, 2017

So, moving by the way the AutoFlushSuspended

Yes, I meant to write it, but forgot

#region IDisposable Support
private bool _disposedValue; // To detect redundant calls

protected virtual void Dispose(bool disposing)
Copy link
Member

Choose a reason for hiding this comment

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

This class is private, so I dont see the reasons to have virtual members and/or implement Disposable "correctly"

Copy link
Member Author

Choose a reason for hiding this comment

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

Default standard snippet from VS, including the anti-redundant call (which is a must for this case).

And well, we could consider than when disposing is false (meaning being called by GC, explicit dispose lacking) we should left the counter incremented for avoiding "fixing" the lack of dispose call through GC and hiding somewhat it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Now moved to SessionImpl, pointless virtual removed.

Copy link
Member

@hazzik hazzik Sep 5, 2017

Choose a reason for hiding this comment

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

But, then we miss finalizer, which should call Dispose(false) then

Copy link
Member Author

@fredericDelaporte fredericDelaporte Sep 5, 2017

Choose a reason for hiding this comment

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

No more needed since Fx 2 I believe, handled by GC without any finalizer. The fact that SessionImpl still does that is an old remnant of Fx 1.

Current recommendations are explicit about not needing a finalizer in almost all cases nowaday.

Copy link
Member

@hazzik hazzik Sep 5, 2017

Choose a reason for hiding this comment

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

But anyway no one is going to call Dispose(false), so I don't think we need to follow this obsolete pattern. Also, I don't think that framework will call Dispose(false) or Dispose() on the finalization stage. We need to prevent calling Dispose() twice, but anyway, without this check we'll get NullReferenceException as session would be unset by the time of second time Dispose() is called. Which, I believe, is clearer than allowing Dispose() to be called twice with no op on the second call.

Copy link
Member Author

Choose a reason for hiding this comment

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

From where is the protected virtual Dispose(boolean disposing) pattern said to be obsolete? Only implementing explicitly a finalizer is obsolete for most cases. This doc seems quite current.

Now for such a little class it should not ever have consequences, but I just rather stick to recommended patterns when they are strongly recommended as in previously linked documentation.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see where it says that finalize is optional:

What I see is:

You must override Finalize to ensure that unmanaged resources are disposed of if your System.IDisposable.Dispose implementation is not called by a consumer of your type.

But again, we implement the IDisposable interface not for it's purpose, but because of convenience of using.

Copy link
Member Author

Choose a reason for hiding this comment

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

The fact is my implementation is invalid for the disposing pattern because I should not touch the managed session when disposing is false: if both were dereferenced, the session could have been GC already and should no more be used in any way.

I am probably wrong by telling the GC call disposing(false) itself, it is likely only useful if some later inheritor decide to implement its own finalizer though this should no more be the way to go (use SafeHandle instead for not having to handle directly the releasing of unmanaged resources).

But well, following the recommended practice just avoids having to think about such details while not being a significant overhead.

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 is the recommended variation and doesn't require overriding the System.Object.Finalize method.

This is in the first point above. The "must" is in the second way of implementing dispose, while the recommended way is the first.

@hazzik
Copy link
Member

hazzik commented Sep 5, 2017

I would implement the helper as following:

class SuspendAutoFlushHelper : IDisposable
{
	SessionImpl _session;
	public SuspendAutoFlushHelper(SessionImpl session)
	{
		_session = session;
		_session._suspendAutoFlushCount++;
	}

	public void Dispose()
	{
		if (_session == null)
			throw new ObjectDisposedException("The SuspendAutoFlushHelper has been disposed already");

		_session._suspendAutoFlushCount--;
		_session = null;
	}
}

This just fit for purpose. The ObjectDisposedException has been just added for those who do not like NullReferenceException in general.

The reason for implementing it like this, because a) we just abusing the using(IDisposable) feature for our mercantile needs, and b) this object is not truly disposable as we do not have any resources to dispose.

@hazzik
Copy link
Member

hazzik commented Sep 5, 2017

Also, there is no point of protected Dispose(bool) as the class is private and don't have any inheritors, so, make it private, and we're good to go.

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Sep 5, 2017

I not really agree with the ObjectDisposedException. An usual dispose should avoid throwing (although some well known classes depart from that, like TransactionScope). This exception is for other methods of the class. The dispose itself should just silently do nothing when called multiple times.

Now for this hijacked dispose, ok, I will code it your way.

(Note: private is not enough, only sealed is. We can derived from a private nested class within the same containing class.)

@fredericDelaporte fredericDelaporte force-pushed the NH-4077-Master branch 2 times, most recently from 1ec68ca to aaee9d9 Compare September 6, 2017 00:05
hazzik
hazzik previously approved these changes Sep 6, 2017
@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Sep 6, 2017

Thanks for reviewing,

I have seen in some previous merge to master than updating the branch (merging from master) then merging back to master was causing some noise like here for the test warnings. (Which actual commits are to be found below on 9 august.)

So I am going to rebase instead, discarding the review by the way.

@hazzik
Copy link
Member

hazzik commented Sep 6, 2017 via email

BhaaLseN and others added 4 commits September 6, 2017 12:09
both fixtures use an evil PostXxxListener to cause/force an auto-flush of
the session that is currently being committed inside a transaction.

for PostInsertFixture it leads to to duplicate inserts.

for PostUpdateFixture it causes an ArgumentOutOfRange exception by clearing
a list that is currently being iterated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants