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.