@@ -92,7 +92,7 @@ protected virtual void JoinSystemTransaction(ISessionImplementor session, System
92
92
originatingSession . TransactionContext = transactionContext ;
93
93
94
94
_logger . DebugFormat (
95
- "enlisted into DTC transaction: {0}" ,
95
+ "enlisted into system transaction: {0}" ,
96
96
transactionContext . EnlistedTransaction . IsolationLevel ) ;
97
97
98
98
originatingSession . AfterTransactionBegin ( null ) ;
@@ -124,19 +124,20 @@ public class SystemTransactionContext : ITransactionContext, IEnlistmentNotifica
124
124
public bool IsInActiveTransaction { get ; internal set ; } = true ;
125
125
126
126
private readonly ISessionImplementor _sessionImplementor ;
127
+ private readonly System . Transactions . Transaction _originalTransaction ;
127
128
private readonly ManualResetEventSlim _lock = new ManualResetEventSlim ( true ) ;
128
129
private volatile bool _needCompletionLocking = true ;
129
130
// Required for not locking the completion phase itself when locking session usages from concurrent threads.
130
131
private readonly AsyncLocal < bool > _bypassLock = new AsyncLocal < bool > ( ) ;
131
132
private readonly int _systemTransactionCompletionLockTimeout ;
132
- private bool IsDistributed => EnlistedTransaction . TransactionInformation . DistributedIdentifier != Guid . Empty ;
133
133
134
134
public SystemTransactionContext (
135
135
ISessionImplementor sessionImplementor ,
136
136
System . Transactions . Transaction transaction ,
137
137
int systemTransactionCompletionLockTimeout )
138
138
{
139
139
_sessionImplementor = sessionImplementor ;
140
+ _originalTransaction = transaction ;
140
141
EnlistedTransaction = transaction . Clone ( ) ;
141
142
EnlistedTransaction . TransactionCompleted += TransactionCompleted ;
142
143
_systemTransactionCompletionLockTimeout = systemTransactionCompletionLockTimeout ;
@@ -159,10 +160,11 @@ public void Wait()
159
160
if ( _lock . Wait ( _systemTransactionCompletionLockTimeout ) )
160
161
return ;
161
162
// A call occurring after transaction scope disposal should not have to wait long, since
162
- // the scope disposal is supposed to block until the transaction has completed: I hope
163
- // that it at least ensures IO are done, even if experience shows DTC lets the scope
164
- // disposal leave before having finished with volatile ressources and
165
- // TransactionCompleted event.
163
+ // the scope disposal is supposed to block until the transaction has completed. When not
164
+ // distributed, all is done, no wait. When distributed, with MSDTC, the scope disposal is
165
+ // left after all prepare phases, and the complete of all resources including the NHibernate
166
+ // one is concurrently raised. So the wait should indeed only have to wait after NHibernate
167
+ // AfterTransaction events.
166
168
// Remove the block then throw.
167
169
Unlock ( ) ;
168
170
throw new HibernateException (
@@ -204,11 +206,18 @@ private void Unlock()
204
206
{
205
207
try
206
208
{
207
- return EnlistedTransaction . TransactionInformation . Status ;
209
+ // Cloned transaction is not disposed "unexpectedly", its status is accessible till context disposal.
210
+ var status = EnlistedTransaction . TransactionInformation . Status ;
211
+ if ( status != TransactionStatus . Active )
212
+ return status ;
213
+
214
+ // The clone status can be out of date when active, check the original one (which could be disposed if
215
+ // the clone is out of date).
216
+ return _originalTransaction . TransactionInformation . Status ;
208
217
}
209
218
catch ( ObjectDisposedException ode )
210
219
{
211
- _logger . Warn ( "Completed transaction was already disposed, unable to get its status ." , ode ) ;
220
+ _logger . Warn ( "Enlisted transaction status was wrongly active, original transaction being already disposed. Will assume neither active nor committed ." , ode ) ;
212
221
return null ;
213
222
}
214
223
}
@@ -225,7 +234,7 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
225
234
{
226
235
if ( _sessionImplementor . ConnectionManager . IsConnected )
227
236
{
228
- using ( _sessionImplementor . ConnectionManager . BeginFlushingFromSystemTransaction ( IsDistributed ) )
237
+ using ( _sessionImplementor . ConnectionManager . BeginFlushingFromSystemTransaction ( ) )
229
238
{
230
239
// Required when both connection auto-enlistment and session auto-enlistment are disabled.
231
240
_sessionImplementor . JoinTransaction ( ) ;
@@ -274,12 +283,7 @@ private void ProcessSecondPhase(Enlistment enlistment, bool? success)
274
283
: "System transaction is in doubt" ) ;
275
284
// we have not much to do here, since it is the actual
276
285
// DB connection that will commit/rollback the transaction
277
- // Usual cases will raise after transaction actions from TransactionCompleted event.
278
- if ( ! success . HasValue )
279
- {
280
- // In-doubt. A durable ressource has failed and may recover, but we won't wait to know.
281
- RunAfterTransactionActions ( false ) ;
282
- }
286
+ // After transaction actions are raised from TransactionCompleted event.
283
287
284
288
enlistment . Done ( ) ;
285
289
}
@@ -289,28 +293,15 @@ private void ProcessSecondPhase(Enlistment enlistment, bool? success)
289
293
290
294
private void TransactionCompleted ( object sender , TransactionEventArgs e )
291
295
{
292
- EnlistedTransaction . TransactionCompleted -= TransactionCompleted ;
293
296
// This event may execute before second phase, so we cannot try to get the success from second phase.
294
297
// Using this event is required by example in case the prepare phase failed and called force rollback:
295
298
// no second phase would occur for this ressource. Maybe this may happen in some other circumstances
296
299
// too.
297
- var wasSuccessful = GetTransactionStatus ( ) == TransactionStatus . Committed ;
298
-
299
- RunAfterTransactionActions ( wasSuccessful ) ;
300
- }
301
-
302
- private volatile bool _afterTransactionActionsDone ;
303
-
304
- private void RunAfterTransactionActions ( bool wasSuccessful )
305
- {
306
- if ( _afterTransactionActionsDone )
307
- // Probably called from In-Doubt and TransactionCompleted.
308
- return ;
309
- _afterTransactionActionsDone = true ;
310
- // Allow transaction completed actions to run while others stay blocked.
311
- _bypassLock . Value = true ;
312
300
try
313
301
{
302
+ EnlistedTransaction . TransactionCompleted -= TransactionCompleted ;
303
+ // Allow transaction completed actions to run while others stay blocked.
304
+ _bypassLock . Value = true ;
314
305
using ( new SessionIdLoggingContext ( _sessionImplementor . SessionId ) )
315
306
{
316
307
// Flag active as false before running actions, otherwise the connection manager will refuse
@@ -324,6 +315,7 @@ private void RunAfterTransactionActions(bool wasSuccessful)
324
315
if ( ! ShouldCloseSessionOnSystemTransactionCompleted )
325
316
_sessionImplementor . ConnectionManager . EnlistIfRequired ( null ) ;
326
317
318
+ var wasSuccessful = GetTransactionStatus ( ) == TransactionStatus . Committed ;
327
319
_sessionImplementor . AfterTransactionCompletion ( wasSuccessful , null ) ;
328
320
foreach ( var dependentSession in _sessionImplementor . ConnectionManager . DependentSessions )
329
321
dependentSession . AfterTransactionCompletion ( wasSuccessful , null ) ;
@@ -368,7 +360,7 @@ private static void Cleanup(ISessionImplementor session)
368
360
// No dispose, done later.
369
361
}
370
362
371
- private volatile bool _isDisposed ;
363
+ private bool _isDisposed ;
372
364
373
365
public void Dispose ( )
374
366
{
@@ -385,7 +377,7 @@ protected virtual void Dispose(bool disposing)
385
377
if ( disposing )
386
378
{
387
379
Unlock ( ) ;
388
- EnlistedTransaction ? . Dispose ( ) ;
380
+ EnlistedTransaction . Dispose ( ) ;
389
381
_lock . Dispose ( ) ;
390
382
}
391
383
}
0 commit comments