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 <[email protected]>

> 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 [email protected].
> 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 [email protected].
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to