I've also been following this thread for a while and I would like to add my
support for a fix. We are getting more and more problems due to the issues
with DTC (crashes, connection leaks etc), so a fix for this would be very
much appreciated.

I'm not sure I can be of any help regarding core development, but if you
need any help with testing or anything else, then I will try to do my best.

However, regarding flush behaviour: IMHO, if you create a NH session inside
a scope it should be the users responsibility to make sure Flush is called.
Just like it's the users responsibility to call Commit if you use a TX
within a session. If the tx scope is created within the session, then maybe
the tx.Complete() should trigger a flush, but not the other way around.
Just my 2 cents.

Anyway, I really appreciate the hard work you're putting into this.

Regards,
Johannes


2014-04-27 11:59 GMT+02:00 Ricardo Peres <rjpe...@gmail.com>:

> Good work, Yves!
>
> IMO, this is one of the most important things to fix in NH.
> TransactionScope is the de facto standard for transactions everywhere in
> the .NET world.
>
> RP
>
> On Saturday, April 26, 2014 9:46:44 PM UTC+1, ydi...@gmail.com wrote:
>>
>> I have been following those issues around TransactionScope for years now,
>> and I was again recently facing problems regarding TS and NH. So I decided
>> to finally have a look at the NH code involved in tx management, and wrote
>> some test cases to confirm some assumptions on the behavior of TS and the
>> DTC. I then found your post and I'm glad you summarized the situation well.
>> I agree with your 3 conclusions: unnecessary use of TxCompleted event
>> handler, flushing in Prepare() is bad, and NH session still needs to enlist
>> (mostly because of 2nd level cache, but not exclusively: entity locks,
>> connections release (?), firing interceptors).
>>
>> Regarding the proposed solutions, here are some comments:
>>
>> - no TransactionCompleted event handler: fully agree, it is redundant
>> with IEnlistmentNotification
>>
>> - no Flush from Prepare(): I would say no Flush() on a connection that
>> has already been enlisted in the same transaction. So that leaves us with 3
>> options: Flush() from Dispose when in TxScope (as you also propose),
>> explicit Flush() required by app code (somehow breaks the current
>> programming model), or Flush from Prepare on another, new connection. The
>> latter is possible if I refer to your comment on the behavior of Prepare():
>> we could enlist a new connection and flush on this one. Now the question
>> is: what is really a resource manager? A DB connection, or the DB server
>> engine behind it? Because in the second case, that option is not doable. A
>> drawback of the third option is also that there is a great chance that the
>> tx will be promoted to distributed if it wasn't already, plus we have to
>> open a new connection, and both are very costly. At first sight, I would
>> vote for Flush() from Dispose, with new FlushMode option, but it does not
>> solve the issue for code scenario 2. That's why we should maybe consider
>> flushing on another connection if possible?
>>
>> - I guess we will not be able to avoid all code in Commit/Rollback. I
>> agree with your idea of having a lock (thinking about a
>> ManualResetEventSlim indicating that no 2PC sequence is ongoing, that is
>> actually how I partly solved this issue on one of my recent projects).
>> However, there is a supplementary challenge here. Indeed, in case of tx
>> timeout, Rollback will be called asynchronously (on a thread pool's thread,
>> since a System.Threading.Timer is firing), without Prepare being called
>> first... Again, we have possible concurrent access to the session, since
>> user code may be running on another thread at the same time. That means
>> that we might need an additional lock on top of the one that would signal
>> an ongoing Prepare/Commit or Rollback sequence, because there is no Prepare
>> in case of timeouts.
>>
>> - of course AfterTransactionCompletion can (and will in case of
>> distributed tx) be called asynchronously
>>
>> - finally, if possible, I would like to make use of the occasion to make
>> things clear about the usage of TxScope and explicit NH transactions. There
>> has always been much confusion about their use together, and no definitive
>> answer: compatible? required to use Begintx with Txscope? why? That
>> confusion has moreover been exacerbated by the buggy support of TxScope in
>> NH. IMHO, an application should be designed to manage transactions using a
>> single, consistent API, and never mix two different tx APIs. So basically,
>> I would require the NH user to make a choice: either use TxScope (with the
>> appropriate TxFactory!), and then making calls to
>> ISession.BeginTransaction() forbidden (throw), or use BeginTx(), with a
>> txfactory that does not support TxScope, and will not enlist the session in
>> any ambient transaction (leave it up to the programmer if the underlying
>> ADO connection automatically enlists if there is an ambient tx, or
>> throw/warning if possible).
>>
>> I would be happy to help implementing a definitive solution, because I
>> have the impression that this issue has not received the attention it
>> deserves until now. This caused some loss of confidence in the NH user
>> community, as the TxScope API is a de facto standard in the .NET world (and
>> recommended over explicit tx management), and supporting it correctly is a
>> must IMHO.
>>
>> Regards,
>>
>> Yves
>>
>> On Sunday, November 24, 2013 8:04:52 PM UTC+1, Oskar Berggren wrote:
>>>
>>> I would guess that new behaviour should be implemented as a new
>>> transaction factory, so that the old sometimes-unstable behaviour can be
>>> used on a case-by-case basis.
>>>
>>> It should be feasible to have the session auto-flush on dispose if
>>> inside a transaction scope. This helps with the 
>>> session-inside-transactionscope
>>> pattern, but not the other way around. (FlushMode.Commit would mean
>>> flush-on-session-dispose-if-inside-transaction). Possibly there should
>>> be new FlushModes to control this.
>>>
>>> It might also be feasible to have a dirty check on transaction scope
>>> disposal (i.e in the prepare phase) but instead of flushing to the
>>> database, it would just throw an error if any dirty state is found. This
>>> would be a double dirty check and could have a significant negative impact
>>> on performance.
>>>
>>> As for unavoidable... When it comes to the abilities of
>>> System.Transaction I have spent some time researching it. I could be wrong,
>>> or maybe there is a different strategy I haven't thought about. I would be
>>> very happy if anyone could come up with a better approach and prove me
>>> wrong.
>>>
>>>
>>>
>>>
>>> 2013/11/24 Michael Teper <mte...@gmail.com>
>>>
>>>> I would really like to have TransactionScope support fixed, but
>>>> requiring a manual Flush is a very high price to pay. We would need to
>>>> track down, fix, and test literally hundreds of places. Is this truly
>>>> unavoidable?
>>>>
>>>> Thanks!
>>>> -Michael
>>>>
>>>>
>>>> On Saturday, November 16, 2013 8:45:46 PM UTC-8, Oskar Berggren wrote:
>>>>
>>>>> We have several issues relating to TransactionScope documented in
>>>>> Jira, and there has been, over the years, multiple, sometimes heated,
>>>>> discussions on this in the mailing lists. The following is an attempt to
>>>>> summarize the abilities and possibilities.
>>>>>
>>>>> https://nhibernate.jira.com/issues/?jql=labels%20%3D%20Trans
>>>>> actionScope%20AND%20status%20in%20%28Open%2C%20%22In%20Progress%22%2C%
>>>>> 20Reopened%2C%20Unconfirmed%29<https://www.google.com/url?q=https%3A%2F%2Fnhibernate.jira.com%2Fissues%2F%3Fjql%3Dlabels%2520%253D%2520TransactionScope%2520AND%2520status%2520in%2520%2528Open%252C%2520%2522In%2520Progress%2522%252C%2520Reopened%252C%2520Unconfirmed%2529&sa=D&sntz=1&usg=AFQjCNE4qup8-JuSqUVyXundHxl2db2yZA>
>>>>>
>>>>>
>>>>> SUMMARY OF WHAT System.Transactions PROVIDES:
>>>>> Based on official documentation, blogs, and experiments.
>>>>>
>>>>> IEnlistmentNotification.Prepare()
>>>>>     May be called on a thread different from the one calling
>>>>> Transaction.Dispose().
>>>>>     Transaction.Dispose() will not return until all enlisted Prepare()
>>>>> have completed.
>>>>>     Can be used to enlist more resource managers, but it cannot touch
>>>>> already
>>>>>     enlisted resource managers, since those may have already been
>>>>> prepared and
>>>>>     could now be locked for further changes in waiting for the second
>>>>> phase.
>>>>>
>>>>> IEnlistmentNotification.Commit()
>>>>>     May be called on a thread different from the one calling
>>>>> Transaction.Dispose().
>>>>>     Might not run until after Transaction.Dispose() has returned.
>>>>>
>>>>> IEnlistmentNotification.Rollback()
>>>>>     May be called on a thread different from the one calling
>>>>> Transaction.Dispose().
>>>>>     I suspect it might not run until after Transaction.Dispose() has
>>>>> returned.
>>>>>     Will not run at all if our own Prepare() initiated the rollback.
>>>>>
>>>>> Transaction.TransactionCompleted
>>>>>     May be called on a thread different from the one calling
>>>>> Transaction.Dispose().
>>>>>     Might not run until after Transaction.Dispose() has returned.
>>>>>     Called for both successful and unsuccessful transactions.
>>>>>     Is documented as an alternative to a volatile enlistment:
>>>>>         "You can register for this event instead of using a volatile
>>>>>           enlistment to get outcome information for transactions."
>>>>> /MSDN
>>>>>     The same MSDN docs also notes that using this event has a negative
>>>>>     performance impact.
>>>>>
>>>>>
>>>>>
>>>>> CURRENT CODE STATE
>>>>> In NHibernate 3.3 (AdoNetWithDistributedTransactionFactory):
>>>>>
>>>>> AdoNetWithDistributedTransactionFactory adds a handler for the
>>>>> TransactionCompleted event and also implements IEnlistmentNotification.
>>>>>
>>>>> A session opened inside a TransactionScope will automatically enlist.
>>>>> It will not flush on dispose, but when the TransactionScope is disposed, a
>>>>> flush will be triggered from the Prepare() phase in
>>>>> AdoNetWithDistributedTransactionFactory. Surprisingly, this sometimes
>>>>> works.
>>>>>
>>>>> Depending on in what order things are done, whether or not the
>>>>> transaction was lightweight or distributed from the start, or if promotion
>>>>> have occurred etc., the code in Prepare() will sometimes hang for 20
>>>>> seconds when trying to flush (waiting on a call to ADO.Net) and then abort
>>>>> (NH-2238). I believe this is because the ADO.Net connection is also a
>>>>> resource manager, enlisted with the transaction, and in some circumstances
>>>>> the transaction manager might have called ADO.Net's Prepare() before
>>>>> NHibernate's. Further attempts to use that connection then blocks while 
>>>>> the
>>>>> connection is waiting for its Commit() or Rollback() notification.
>>>>> Generally speaking, I would say that while it is allowed to enlist more
>>>>> resource managers during the prepare phase, it is _not_ allowed to touch
>>>>> other already enlisted resource managers, because there is no ordering
>>>>> guarantee.
>>>>>
>>>>> The current code is written with the assumption that
>>>>> TransactionCompleted and Commit()/Rollback() is triggered before
>>>>> Transaction.Dispose() returns. They call ISessionImplementor.
>>>>> AfterTransactionCompletion() and CloseSessionFromDistributedTransaction().
>>>>> This causes race conditions in the session if the user keeps working with
>>>>> it (NH-2176). It is also a problem for the ADO.Net connection, since that
>>>>> is also not thread safe and so while 
>>>>> CloseSessionFromDistributedTransaction()
>>>>> is trying to close the connection, the transaction manager may have used a
>>>>> different thread to issue the commit phase on the connection.
>>>>>
>>>>> Since they may in fact be called later, this causes multi-thread
>>>>> issues with both the ISession and the ADO.Net connection. This is because
>>>>> this separate thread will call 
>>>>> ISessionImplementor.AfterTransactionCompletion()
>>>>> and CloseSessionFromDistributedTransaction() which will touch the
>>>>> session (that the application itself may already use for something else
>>>>> (NH-2176)) and also try to close the connection, which might cause
>>>>> connection pool corruption (NH-3023).
>>>>>
>>>>>
>>>>> CONCLUSIONS
>>>>> * It is redundant to both implement IEnlistmentNotification and listen
>>>>> for TransactionCompleted.
>>>>>
>>>>> * Flushing changes to the ADO.Net connection in
>>>>> IEnlistmentNotification.Prepare(), as we currently do, IS SIMPLY NOT
>>>>> SUPPORTED, because we cannot touch the database connection since that is
>>>>> itself a resource manager already enlisted.
>>>>>
>>>>> * We might need to still enlist with the transaction to release second
>>>>> level cache locks.
>>>>>
>>>>>
>>>>> CODE SCENARIO 1: SESSIONS INSIDE TRANSACTION
>>>>>
>>>>> using (transactionscope)
>>>>> {
>>>>>     // WITH EXPLICIT NH TRANSACTION.
>>>>>     using (new session)
>>>>>     using (session.BeginTransaction)
>>>>>     {
>>>>>         DoEntityChanges();
>>>>>
>>>>>         // This will flush the above changes, but leave the underlying
>>>>> db
>>>>>         // connection and transaction open.
>>>>>         tx.Commit();
>>>>>     }
>>>>>
>>>>>     // WITHOUT EXPLICIT NH TRANSACTION.
>>>>>     using (new session)
>>>>>     {
>>>>>         DoEntityChanges();
>>>>>
>>>>>         // This will flush the above changes, but leave the underlying
>>>>> db
>>>>>         // connection and transaction open.
>>>>>         session.Flush();
>>>>>     }
>>>>>
>>>>>     // Because of the current flush during the prepare phase, this
>>>>> change might
>>>>>     // be committed despite being performed outside a transaction. It
>>>>> might also
>>>>>     // cause a deadlock. Without this change, the flush-in-prepare
>>>>> will have nothing
>>>>>     // to do, and so will not cause a problem.
>>>>>     entity.AdditionalChange()
>>>>>
>>>>>     transactionscope.Complete();
>>>>> }
>>>>>
>>>>>
>>>>> (Improved) Support for sessions inside TransactionScope is subject to
>>>>> the following limitations:
>>>>> * Changes must be flushed explicitly before reaching
>>>>> TransactionScope.Dispose().
>>>>>
>>>>> * We need to fix the code so that we also never touch the ADO.Net
>>>>> connection after
>>>>>   TransactionScope.Dispose() has been reached. To achieve this, the
>>>>> session should
>>>>>   release the connection no later than its own Dispose(). NH-2928.
>>>>> This would also
>>>>>   reduce the need for transaction promotion when two non-overlapping
>>>>> sessions are
>>>>>   used within the same TransactionScope.
>>>>>
>>>>> * If desired, it should be possible to have the session auto-flush
>>>>> from its Dispose()
>>>>>   when used inside a TransactionScope (NH-2181). Drawback is that the
>>>>> session
>>>>>   would behave very differently compared to not using TransactionScope
>>>>> (increased
>>>>>   complexity).
>>>>>
>>>>> * Entity changes performed outside an active transaction would not be
>>>>> committed,
>>>>>   just as when not using TransactionScope. Unless the entity is
>>>>> reattached to a
>>>>>   new session.
>>>>>
>>>>>
>>>>>
>>>>> CODE SCENARIO 2: TRANSACTIONS INSIDE SESSION
>>>>>
>>>>> using (new session)
>>>>> {
>>>>>     using (new transactionscope)
>>>>>     {
>>>>>         DoEntityChanges();
>>>>>
>>>>>         transactionscope.Complete();
>>>>>     }
>>>>>
>>>>>     // Unknown amount of time before the transaction commit phase is
>>>>> done with the session.
>>>>>
>>>>>     using (new transactionscope)
>>>>>     {
>>>>>         DoEntityChanges();
>>>>>
>>>>>         transactionscope.Complete();
>>>>>     }
>>>>> }
>>>>>
>>>>>
>>>>> (Improved) Support for TransactionScope inside sessions is subject to
>>>>> the following limitations:
>>>>> * We simply cannot rely on the TransactionScope to tell us when to
>>>>> flush, so again this can
>>>>>   only work with explicit flush (either Flush() or Commit() on NH's
>>>>> transaction).
>>>>>
>>>>> * To support consecutive TransactionScopes (NH-2176), or really any
>>>>> use of the session after we have reached
>>>>>   Dispose() on the first TransactionScope, the application must be
>>>>> able to reliable detect when the
>>>>>   out-of-band Commit()/Rollback in IEnlistmentNotification have
>>>>> completed.
>>>>>
>>>>>
>>>>>
>>>>> COMPARISON WITH OTHER SOFTWARE
>>>>> It has been claimed that other software "works with
>>>>> System.Transactions automatically and doesn't require the use of something
>>>>> like NH's ITransaction". These statements seem to be both correct and
>>>>> wrong. Looking at eg. Linq2SQL and Entity Framework, it's true that they
>>>>> have no counterpart to NH's ITransaction. However, they do require 
>>>>> explicit
>>>>> calls to e.g. SaveChanges() before TransactionScope.Complete() and
>>>>> Dispose() is called. So they too have no automatic
>>>>> flush-on-TransactionScope.Dispose() behaviour.
>>>>>
>>>>> Within the context of a TransactionScope, one can perceive the
>>>>> ITransaction.Commit() as one possibly way to trigger the explicit flush of
>>>>> changes. The other one is of course session.Flush(). So it's really not
>>>>> that different.
>>>>>
>>>>>
>>>>>
>>>>> IDEAS FOR PROPOSED SOLUTION
>>>>> * Use only IEnlistmentNotification, not TransactionCompleted.
>>>>>
>>>>> * Remove the flushing from IEnlistmentNotification.Prepare(). The
>>>>> application must flush earlier or loose the changes.
>>>>>
>>>>> * If we cannot move all code from the Commit/Rollback/TransactionCompleted
>>>>> stage into Prepare(),
>>>>>   we must instead grab some sort of lock on the session at the start
>>>>> of Prepare(). The lock would be
>>>>>   released at the end of the Commit or Rollback phase. In the
>>>>> meantime, all other attempts to use
>>>>>   the session should block waiting for the lock to be released. (I
>>>>> suspect this is what happens when use of
>>>>>   the ADO.Net connection blocks when flushing from Prepare().)
>>>>>
>>>>> * Document that IInterceptor.AfterTransactionCompletion() and similar
>>>>> may be called
>>>>>   asynchronously on a different thread.
>>>>>
>>>>> * Optionally, we can also add automatic flush in the session's
>>>>> Dispose() method if an ambient
>>>>>   transaction is detected.
>>>>>
>>>>>
>>>>> I'm still a bit unclear on the interaction with the second level cache.
>>>>>
>>>>>
>>>>> /Oskar
>>>>>
>>>>>  --
>>>>
>>>> ---
>>>> You received this message because you are subscribed to the Google
>>>> Groups "nhibernate-development" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to nhibernate-development+unsubscr...@googlegroups.com.
>>>> For more options, visit https://groups.google.com/groups/opt_out.
>>>>
>>>
>>>  --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "nhibernate-development" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to nhibernate-development+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 

--- 
You received this message because you are subscribed to the Google Groups 
"nhibernate-development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to nhibernate-development+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to