@@ -243,9 +243,9 @@ public virtual void Wait()
243243 // Remove the block then throw.
244244 Unlock ( ) ;
245245 throw new HibernateException (
246- $ "Synchronization timeout for transaction completion. Either raise" +
247- $ "{ Cfg . Environment . SystemTransactionCompletionLockTimeout } , or check all scopes are properly" +
248- $ "disposed and/or all direct System.Transaction.Current changes are properly managed.") ;
246+ "A synchronization timeout occurred at transaction completion. Either raise " +
247+ $ "{ Cfg . Environment . SystemTransactionCompletionLockTimeout } , or check all scopes are properly " +
248+ "disposed and/or all direct System.Transaction.Current changes are properly managed." ) ;
249249 }
250250 catch ( HibernateException )
251251 {
@@ -452,6 +452,7 @@ protected virtual void CompleteTransaction(bool isCommitted)
452452 // do an early exit here in such case.
453453 if ( ! IsInActiveTransaction )
454454 return ;
455+ var isSessionProcessing = _session . IsProcessing ( ) ;
455456 try
456457 {
457458 // Allow transaction completed actions to run while others stay blocked.
@@ -460,22 +461,27 @@ protected virtual void CompleteTransaction(bool isCommitted)
460461 // cancelled on a new thread even for non-distributed scopes. So, the session could be doing some processing,
461462 // and will not be interrupted until attempting some usage of the connection. See #3355.
462463 // Thread safety of a concurrent session BeginProcess is ensured by the Wait performed by BeginProcess.
463- var isProcessing = _session . IsProcessing ( ) ;
464- if ( isProcessing )
464+ if ( isSessionProcessing )
465465 {
466466 var timeOutGuard = new Stopwatch ( ) ;
467467 timeOutGuard . Start ( ) ;
468- while ( isProcessing && timeOutGuard . ElapsedMilliseconds < _systemTransactionCompletionLockTimeout )
468+ while ( isSessionProcessing && timeOutGuard . ElapsedMilliseconds < _systemTransactionCompletionLockTimeout )
469469 {
470470 // Naïve yield.
471471 Thread . Sleep ( 10 ) ;
472- isProcessing = _session . IsProcessing ( ) ;
472+ isSessionProcessing = _session . IsProcessing ( ) ;
473+ }
474+ if ( isSessionProcessing )
475+ {
476+ // Throwing would give up attempting to close the session if need be, which may still succeed. So,
477+ // just log an error.
478+ _logger . Error (
479+ "A synchronization timeout occurred at transaction completion: the session is still processing. Attempting " +
480+ "to finalize the transaction concurrently, which may cause thread safety failure. You may " +
481+ "raise {0} if it is set too low. It may also be a limitation of the data provider, like not " +
482+ "supporting transaction scope timeouts occurring on concurrent threads." ,
483+ Cfg . Environment . SystemTransactionCompletionLockTimeout ) ;
473484 }
474- if ( isProcessing )
475- throw new HibernateException (
476- $ "Synchronization timeout for transaction completion. Either raise" +
477- $ "{ Cfg . Environment . SystemTransactionCompletionLockTimeout } , or check all scopes are properly" +
478- $ "disposed and/or all direct System.Transaction.Current changes are properly managed.") ;
479485 }
480486 using ( _session . BeginContext ( ) )
481487 {
@@ -516,6 +522,15 @@ protected virtual void CompleteTransaction(bool isCommitted)
516522 // Dispose releases blocked threads by the way.
517523 Dispose ( ) ;
518524 }
525+
526+ if ( isSessionProcessing )
527+ {
528+ throw new HibernateException (
529+ "A synchronization timeout occurred at transaction completion: the session was still processing. You may " +
530+ $ "raise { Cfg . Environment . SystemTransactionCompletionLockTimeout } if it is set too low. It may also " +
531+ "be a limitation of the data provider, like not supporting transaction scope timeouts occurring on " +
532+ "concurrent threads." ) ;
533+ }
519534 }
520535
521536 private static void Cleanup ( ISessionImplementor session )
0 commit comments