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%20TransactionScope%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.

Reply via email to