-
Notifications
You must be signed in to change notification settings - Fork 934
NH-2176 - Add test case #410
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
Need to cleanup whitespaces |
Related to #532 |
@fredericDelaporte I need someone with fresh eyes to look into this |
I may be completely wrong, but I fear NHibernate tries to support an invalid programming model about transaction scope currently.
It looks to me that point 4 is an attempt at supporting a model that violates If we look at how this is handled in EF 6 (doc updated on October 2016), it dodges the trouble in a quite more valid way:
That way, the context is indeed no more needed by the transaction scope for completing the transaction. Point 1. is important for allowing other contextes nested inside the transaction scope to access the changes done on the data by a previous context while still in the ongoing transaction scope. (Otherwise they have this issue.) I think we need to do breaking changes for having a more reasonable implementation:
Well ideally, we should still perform post-transaction operations required for things like the second level cache, although the session is disposed. I do not know if those operations can be moved outside session responsibility easily or not. An additional point could be added for an alternate to point 1. of EF case:
|
Checking the state of Hibernate, for the JPA case, they do something similar to NHibernate delayed disposal of the session. But for the JDBC and JTA (JTA is what looks closest to I think we should replicate the JDBC handling. |
Still more thoughts on the subject: currently on NHibernate side, it looks like people try to use the old entity mode switching capability ( We are quite diverging from Hibernate by doing so. They do not need this because they have decoupled connexion handling from the session. The more I look to all that, the more I think we should re-align our session/transaction/connection handling with Hibernate. |
I agree |
2ddb025
to
47070c0
Compare
It seems that |
It will very likely fix NH-2176, but it will not fix:
Unlikely to fix:
That is why I consider this change is not enough to fix current approach. |
NH-2238 is exactly the same as NH-3968 |
(Well, the time for me to consolidate my previous comment, it seems you have met NH-3968. A comment notification on #532 indicates that in my mailbox, but I do not see it online.) |
I deleted it along with the changes it was mentioning |
Having the same root cause, very likely. But the former writes about a closed connection exception while the later is about a timeout trying to use it.
I'd guessed you have deleted it of course, but sometime I prefer tell facts (not seeing it) rather than guesses (being deleted) even when obvious. (And sometime that's Github auto-loading of new comments which does not always trigger when I would have expected.) |
47070c0
to
91c7aa3
Compare
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 custom transaction factory is in a dead end unless we do some code changes in NHibernate.
@@ -19,6 +19,11 @@ public class DtcFailuresFixture : TestCase | |||
{ | |||
private static readonly ILog log = LogManager.GetLogger(typeof(DtcFailuresFixture)); | |||
|
|||
protected override void Configure(Configuration configuration) | |||
{ | |||
configuration.SetProperty(Cfg.Environment.TransactionStrategy, "NHibernate.Test.NHSpecificTest.NH2176.CustomAdoNetTransactionFactory, NHibernate.Test"); |
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.
Testing the custom factory with DTC testes.
if (Dialect is FirebirdDialect) | ||
Assert.Ignore("Firebird driver does not support distributed transactions"); | ||
/*if (Dialect is FirebirdDialect) | ||
Assert.Ignore("Firebird driver does not support distributed transactions");*/ |
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.
In my local test Firebird supports them (at least does not throw), so just letting them run to test with teamcity too.
@@ -113,8 +118,8 @@ public void Can_roll_back_transaction() | |||
[Description("Another action inside the transaction do the rollBack outside nh-session-scope.")] | |||
public void RollbackOutsideNh() |
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 one showcase the roadblock for the custom transaction factory: session close within an ongoing transaction scope is rejected by connection manager.
The connection manager close does close any pending explicit transaction, then call disconnect which checks there are no pending transaction, explicit or ambient. The ambient check on disconnect should be removed, ambient transaction supports connection disconnect.
This need changes in NHibernate project even for testing: the connection manager is not pluggable.
// This damn thing may yield an invalid operation exception (instead of null | ||
// or of a completed transaction) if we are between scope.Complete() and | ||
// scope.Dispose(). This happen when having completed the scope then disposing | ||
// the session and only after that disposing the scope. |
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.
Another bug fixed in custom transaction factory: accessing Transaction.Current may throw, need to catch.
Doomed attempt, I have seen locally that the second phase of 2PC can be executed after transaction scope disposal. (Illustrated by NH-2420 test.) I need to do some new edits on NH-4011! I may still add one more attempt, if I find a way through lock to ensure second phase is done before any new session usage. But it may be a crutch reducing risks but not eliminating them. |
9c8147b
to
5d1fbdf
Compare
using (var s = sessions.OpenSession(connection)) | ||
{ | ||
Nums nums = null; | ||
Assert.DoesNotThrow(() => nums = s.Load<Nums>(29), "Failed loading entity from second level 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.
Leaving an explicit message instead of "caching failure" => then "failing to tear down", and letting not much clues.
|
||
using (var s = sessions.OpenSession()) | ||
using (var tx = s.BeginTransaction()) | ||
finally |
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.
Avoiding additional tear down failure.
5d1fbdf
to
fafc546
Compare
fafc546
to
2a4c2a5
Compare
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.
That attempt seems now to work completely with SQL Server, even DTC test that was ignored as "not fixed" being green.
But relying on thread synchronization does look good to me. I would rather introduce the new factories I envision in NH-4011.
Of course, need to see if this works with all db still. And then, need to cleanup previous tests (custom factory, commits, ...)
// here. When having TransactionCompleted event, this event and the second phase | ||
// tend to occur before reaching here. But some other NH cases demonstrate that | ||
// TransactionCompleted may also occur "too late". | ||
((ISessionImplementor) s).TransactionContext?.WaitOne(); |
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 test use a session property (IsConnected
) that does not sync with Transaction factory. So for the test, I have added an explicit sync call. I was feeling like really required to cause IsConnected
to sync.
public IDisposable FlushingFromDtcTransaction | ||
{ | ||
get | ||
{ | ||
if (ownConnection) |
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.
Not trying to handle use supplied connection case. We would have to clone it otherwise.
@@ -24,52 +25,36 @@ public ITransaction CreateTransaction(ISessionImplementor session) | |||
|
|||
public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session) | |||
{ | |||
// Ensure the session does not run on a thread supposed to be blocked, waiting | |||
// for transaction completion. | |||
session.TransactionContext?.WaitOne(); |
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.
Transaction completion may concurrently nullify it, but ?.
handles that (correctly evaluate it only once, guaranteeing it will not try to call WaitOne
on null).
|
||
private readonly ISessionImplementor _sessionImplementor; | ||
private readonly ManualResetEvent _waitEvent = new ManualResetEvent(true); | ||
private readonly AsyncLocal<bool> _bypassWait = new AsyncLocal<bool>(); |
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.
Not already needing async but well, better avoid adding new thread locals.
// Dispose releases blocked threads by the way. | ||
// Must dispose in case !ShouldCloseSessionOnDistributedTransactionCompleted, since | ||
// we nullify session TransactionContext, causing it to have nothing still holding it. | ||
Dispose(); |
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 missing and is a transaction leak in NHibernate 4.1. When scopes are disposed inside session life-span, the TransactionContext
was nullified on session without being disposed, causing the AmbientTransaction
clone to not be disposed of explicitly.
{ | ||
_logger.Warn( | ||
"Synchronization failure, assuming it has been concurrently disposed and do not need sync anymore.", | ||
ex); |
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 have not witnessed this, but it may happen if between the test on _isDisposed
and the call to WaitOne
, the transaction completion event dispose the wait event.
AmbientTransation = null; | ||
} | ||
_waitEvent.Set(); | ||
_waitEvent.Dispose(); |
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.
In my tests, disposing right after signaling does not cause an exception for thread potentially waiting. (I have run all test first without the catch around WaitOne
, and do not got any failures, all tests passed.)
// here. When having TransactionCompleted event, this event and the second phase | ||
// tend to occur before reaching here. But some other NH cases demonstrate that | ||
// TransactionCompleted may also occur "too late". | ||
((ISessionImplementor) s).TransactionContext?.WaitOne(); |
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.
shall be s.GetSessionImplementor()
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.
Probably not the first time... Maybe should we remove all the other alike catches which still liter the tests.
void IEnlistmentNotification.Rollback(Enlistment enlistment) | ||
#endregion | ||
|
||
public void TransactionCompleted(object sender, TransactionEventArgs e) |
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.
TransactionCompleted shall be incorporated to IEnlistmentNotification.Commit/IEnlistmentNotification.Rollback/IEnlistmentNotification.InDoubt
e.Transaction.TransactionCompleted -= TransactionCompleted; | ||
// This event may execute before second phase, so we cannot try to get the success from second phase. | ||
// Using this event is required in case the prepare phase failed and called force rollback: no second | ||
// phase would occur for this ressource. |
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 shall be handled on force rollback then
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.
Documentation on which conditions the second phase will not be called at all lacks. At first I have put it all in second phase callbacks, just to realize thanks to some tests there were cases for which it was not good.
So the forced rollback from session volatile prepare phase is maybe only one case in which second phase do not occur. For staying on the safe side, we would better keep that handling in TransactionCompleted
.
The fact the completion actions were called from in-doubt too made me wonder whether in in-doubt case, the transaction completed call be not called either. There too, for staying on the safe side, I have decided to keep that call. This transaction factory is the result of many years of fiddling with many failing case for trying to support them all. During my own fiddling with it, I have seen that many design decisions in it, lacking comments and which were looking as overly complicating the code, where indeed needed for some reasons. So I had to kept them, but at least adding comments for most of them.
Moreover, handling transaction completion actions from the prepare phase in case of force rollback may cause other issues, since the session connection state at that point is unknown: has it already processed its own prepare phase? Is it processing it concurrently? Raising completion actions there would trigger its release, and I do not know if all connection implementations would support that.
This is also why I consider we should still provide other transaction factories even if we appear to get this one working: there too many points where we still act on the connection with concurrency risks. Providing other transaction factories would allow whoever wishing it to not take those risks, at the cost of some more explicit flushes calls.
No description provided.