@@ -170,6 +170,9 @@ void IEnlistmentNotification.InDoubt(Enlistment enlistment)
170
170
171
171
private void ProcessSecondPhase ( Enlistment enlistment , bool ? success )
172
172
{
173
+ // In case of rollback, the prepare phase may have not be run, and we then need to lock as soon as possible.
174
+ // There is no guarantee the second phase will be run before the completed event, doing that at both places.
175
+ SafeLockSession ( ) ;
173
176
using ( new SessionIdLoggingContext ( _sessionImplementor . SessionId ) )
174
177
{
175
178
_logger . Debug (
@@ -195,6 +198,9 @@ private void ProcessSecondPhase(Enlistment enlistment, bool? success)
195
198
196
199
public void TransactionCompleted ( object sender , TransactionEventArgs e )
197
200
{
201
+ // In case of rollback, the prepare phase may have not be run, and we then need to lock as soon as possible.
202
+ // There is no guarantee the second phase will be run before the completed event, doing that at both places.
203
+ SafeLockSession ( ) ;
198
204
e . Transaction . TransactionCompleted -= TransactionCompleted ;
199
205
// This event may execute before second phase, so we cannot try to get the success from second phase.
200
206
// Using this event is required by example in case the prepare phase failed and called force rollback:
@@ -247,6 +253,23 @@ private void RunAfterTransactionActions(bool wasSuccessful)
247
253
}
248
254
}
249
255
256
+ /// <summary>
257
+ /// Lock the session, but do not assume the context is not already disposed or concurrently disposing.
258
+ /// </summary>
259
+ private void SafeLockSession ( )
260
+ {
261
+ if ( _isDisposed )
262
+ return ;
263
+ try
264
+ {
265
+ _waitEvent . Reset ( ) ;
266
+ }
267
+ catch
268
+ {
269
+ // Ignore, concurrently disposing, meaning it does not need to block anymore.
270
+ }
271
+ }
272
+
250
273
private volatile bool _isDisposed ;
251
274
252
275
public void Dispose ( )
0 commit comments