Hi,
Alexander has pointed me here, which summarize most of the findings I have 
(re)done while working on that subject. (I had search the dev list before 
starting tackling the subject but unfortunately not found that thread 
before, so long for me.)

Here are some complementary notes.

*Concurrency*

The first Oskar message is a great summary, but is a bit unclear about 
concurrency issues with transaction completion event and second phase 
callback (commit/rollback/in-doubt).
When the transaction is distributed, they can all execute concurrently to 
code following the scope disposal. Scope dispose does block only till all 
prepare phases have voted and MSDTC has recorded the outcome. After that, 
all remaining work (en of scope disposal, transaction complete event, 
second phase callbacks) run concurrently.

You can read more about that in following discussion 
<https://github.com/npgsql/npgsql/issues/1571#issuecomment-308651461> with 
a Microsoft employee working on MSDTC.

*Usage pattern*

> The recommendation from NH for many years have been to always use the NH 
transaction even when running inside transaction scope (indeed this is 
really the only truly supported scenario). 

This is terribly unfortunate.

Quote from an old Msdn article 
<https://msdn.microsoft.com/en-us/library/ms973865.aspx>, emphasis mine:

> As a rule, use System.Transactions in the general case, and use resource 
manager internal transactions only in specific cases where you are certain 
the transaction will not span multiple resources, and will not be composed 
into a larger transaction. *Never mix the two*.

Other point against mixing both, if a local transaction is started first, 
it is no more possible to enlist the connection inside a scope, as stated in 
Microsoft doc 
<https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/distributed-transactions#manually-enlisting-in-a-distributed-transaction>
:

> EnlistTransaction throws an exception if the connection has already begun 
a transaction using the connection's BeginTransaction method.

This means that starting a local transaction then a scope cause at best the 
scope to be ignored (current NHibernate state) or at worst will cause the 
session to fail (if it starts trying explicitly enlisting the connection 
into ambient scope).

*Transaction completion event*

About replacing transaction completion for second phase enlistment, I have 
tried it and that was causing the issues to be worst. I guess the trouble 
is closing the connection from second phase callbacks tends to more 
frequently causes issues than from transaction completion event.

*Multiple connections to same database inside a transaction*

The semantic for locks is normally to be bound to the transaction, not to 
the connection. Having concurrent connections in the same transaction 
should not cause them to lock themselves.

About the SQL Server behavior of not promoting to distributed when 
sequentially using many connection, I can tell Npgsql supports this too. 
(Its pool keeps track of connections enlisted in transaction, returned to 
pool, and yield them back if a connection is requested with the same 
ambient transaction.)


*What I have done so far*

There is a PR <https://github.com/nhibernate/nhibernate-core/pull/627> 
which fixes all distributed transaction failing test cases, adds a lot more 
tests and supports them. It still needs some cleanup and discussion, but 
that looks quite more robust than current NHibernate state.

It does not add a new transaction factory, but it fixes the existing one 
for most cases with (I think) sensible possible breaking changes. By 
default it does not completely fix the troubles bound to connection release 
from transaction completion, but it provides an option for that. That 
option causes "flush on commit" to be disabled for transaction scope, and 
so require the changed to be explicitly flushed.

Note that even without "flush on commit" disabled, no tests are failing. 
But some unwanted promotion to distributed may occur, and in some corner 
cases bad performances can be observed. (This occurs when nesting session 
life-cycle inside scope, keeping the flush from commit enabled, 
encountering a deadlock, trying again with new session, encountering a 
deadlock again, ... In this pattern, the connection releasing is more and 
more slow. But the connection pool corruption does not more occurs since 
.Net Framework 4.0. It seems the pool has become more robust.)

Notable changes:

   - When flushing from commit (so in prepare phase), it will use a new 
   connection dedicated to that, unless the database does not support it (like 
   SQLite). Can be "hacked back" to previous behavior by overriding the 
   dialect for it to tell it does not support concurrent connection inside the 
   same transaction.
   May trigger undesired promotion to distributed.
   The recommended way to avoid that is to disable the "flush from commit" 
   functionality for system transaction, see below.
   - It will lock session usage from the point where a system transaction 
   cease to be active to the end of completion event tracked by the NHibernate 
   resource. This works even in cases where the prepare phase is not called, 
   and prevents session usage after the scope disposal, while the transaction 
   completion is not yet ended. The code using the session after the scope is 
   blocked from executing till completion end.
   - It will forbid any session connection usage from ISession/IInterceptor 
   AfterTransactionCompletion event when it is raised from system transaction 
   completion. Attempt to use the connection will throw. (Unless hacking for 
   getting a reference on it soon enough and use it directly instead of going 
   through connection manager.)
   - The connection will no more be actually released from 
   ConnectionManager AfterTransaction event when called from system 
   transaction completion. It will instead be flagged for releasing, and will 
   be released at next connection manager GetConnection usage (or when 
   connection manager is closed).
   With this changes, in most cases, they will be no more connection 
   releases from system transaction completion threads.
   If some user code was retaining the session without acting on it for a 
   long period after the last transaction, this could be a detrimental change, 
   causing the connection to be released quite later.
   - The session will now actively enlist its connection inside the ambient 
   transaction scope if any. This can be disabled by switching to the 
   transaction factory unaware of system transaction, or by using an option at 
   session opening which causes system transaction to be completely ignored by 
   NHibernate. For consistency, the connection string should then specify 
   Enlist=false.
   So this can be a possible breaking change if some user code was opening 
   a NHibernate transaction, then a scope, then was using the session. 
   Previously the scope was ignored and the connection not participating in 
   it, essentially meaning the user transaction was silently broken. Now it 
   will throw.
   - An "UseConnectionFromSystemTransactionEvents"  option is added, true 
   by default.
   When set to false, this forbid using the connection from ISession 
   BeforeTransactionCompletion event when raised from system transaction, 
   which disable flush from commit. (No exception, just no flush.) If nesting 
   session life-cycle inside transaction scope, this option must be disabled 
   for avoiding having the session actually closed from transaction completion 
   (and retaining its connection till there).
   When set to false, session disposed inside a scope will be actually 
   disposed of. But their transaction completion events will still be called, 
   notably allowing second level cache to still works (I have put tests for 
   ascertaining this). Of course this is a bit debatable to still call some 
   methods on a disposed object. Thus it could be better to port the 
   transaction event queue from current Hibernate, which seems to allow 
   decoupling a bit more those transaction events from the (potentially 
   disposed) session.


-- 

--- 
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