@@ -243,9 +243,9 @@ public virtual void Wait()
243
243
// Remove the block then throw.
244
244
Unlock ( ) ;
245
245
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." ) ;
249
249
}
250
250
catch ( HibernateException )
251
251
{
@@ -452,6 +452,7 @@ protected virtual void CompleteTransaction(bool isCommitted)
452
452
// do an early exit here in such case.
453
453
if ( ! IsInActiveTransaction )
454
454
return ;
455
+ var isSessionProcessing = _session . IsProcessing ( ) ;
455
456
try
456
457
{
457
458
// Allow transaction completed actions to run while others stay blocked.
@@ -460,22 +461,27 @@ protected virtual void CompleteTransaction(bool isCommitted)
460
461
// cancelled on a new thread even for non-distributed scopes. So, the session could be doing some processing,
461
462
// and will not be interrupted until attempting some usage of the connection. See #3355.
462
463
// 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 )
465
465
{
466
466
var timeOutGuard = new Stopwatch ( ) ;
467
467
timeOutGuard . Start ( ) ;
468
- while ( isProcessing && timeOutGuard . ElapsedMilliseconds < _systemTransactionCompletionLockTimeout )
468
+ while ( isSessionProcessing && timeOutGuard . ElapsedMilliseconds < _systemTransactionCompletionLockTimeout )
469
469
{
470
470
// Naïve yield.
471
471
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 ) ;
473
484
}
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.") ;
479
485
}
480
486
using ( _session . BeginContext ( ) )
481
487
{
@@ -516,6 +522,15 @@ protected virtual void CompleteTransaction(bool isCommitted)
516
522
// Dispose releases blocked threads by the way.
517
523
Dispose ( ) ;
518
524
}
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
+ }
519
534
}
520
535
521
536
private static void Cleanup ( ISessionImplementor session )
0 commit comments