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.