@@ -26,48 +26,24 @@ public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session)
26
26
{
27
27
if ( session . TransactionContext != null )
28
28
return ;
29
-
30
- if ( System . Transactions . Transaction . Current == null )
29
+
30
+ var transaction = System . Transactions . Transaction . Current ;
31
+ if ( transaction == null )
31
32
return ;
32
-
33
- var transactionContext = new DistributedTransactionContext ( session ,
34
- System . Transactions . Transaction . Current ) ;
33
+
34
+ if ( session . ConnectionManager . RequireExplicitEnlistment )
35
+ {
36
+ // Will fail if the connection is already enlisted in another not yet completed transaction.
37
+ // Probable case: nested transaction scope. Supporting this could be done by releasing the
38
+ // connection instead of enlisting.
39
+ session . Connection . EnlistTransaction ( transaction ) ;
40
+ }
41
+ var transactionContext = new DistributedTransactionContext ( session , transaction ) ;
35
42
session . TransactionContext = transactionContext ;
36
43
logger . DebugFormat ( "enlisted into DTC transaction: {0}" ,
37
44
transactionContext . AmbientTransation . IsolationLevel ) ;
38
45
session . AfterTransactionBegin ( null ) ;
39
46
40
- TransactionCompletedEventHandler handler = null ;
41
-
42
- handler = delegate ( object sender , TransactionEventArgs e )
43
- {
44
- using ( new SessionIdLoggingContext ( session . SessionId ) )
45
- {
46
- ( ( DistributedTransactionContext ) session . TransactionContext ) . IsInActiveTransaction = false ;
47
-
48
- bool wasSuccessful = false ;
49
- try
50
- {
51
- wasSuccessful = e . Transaction . TransactionInformation . Status
52
- == TransactionStatus . Committed ;
53
- }
54
- catch ( ObjectDisposedException ode )
55
- {
56
- logger . Warn ( "Completed transaction was disposed, assuming transaction rollback" , ode ) ;
57
- }
58
- session . AfterTransactionCompletion ( wasSuccessful , null ) ;
59
- if ( transactionContext . ShouldCloseSessionOnDistributedTransactionCompleted )
60
- {
61
- session . CloseSessionFromDistributedTransaction ( ) ;
62
- }
63
- session . TransactionContext = null ;
64
- }
65
-
66
- e . Transaction . TransactionCompleted -= handler ;
67
- } ;
68
-
69
- transactionContext . AmbientTransation . TransactionCompleted += handler ;
70
-
71
47
transactionContext . AmbientTransation . EnlistVolatile ( transactionContext ,
72
48
EnlistmentOptions . EnlistDuringPrepareRequired ) ;
73
49
}
@@ -138,37 +114,44 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
138
114
}
139
115
140
116
void IEnlistmentNotification . Commit ( Enlistment enlistment )
141
- {
142
- using ( new SessionIdLoggingContext ( sessionImplementor . SessionId ) )
143
- {
144
- logger . Debug ( "committing DTC transaction" ) ;
145
- // we have nothing to do here, since it is the actual
146
- // DB connection that will commit the transaction
147
- enlistment . Done ( ) ;
148
- IsInActiveTransaction = false ;
149
- }
150
- }
117
+ => ProcessSecondPhase ( enlistment , true ) ;
151
118
152
119
void IEnlistmentNotification . Rollback ( Enlistment enlistment )
153
- {
154
- using ( new SessionIdLoggingContext ( sessionImplementor . SessionId ) )
155
- {
156
- logger . Debug ( "rolled back DTC transaction" ) ;
157
- // Currently AfterTransactionCompletion is called by the handler for the TransactionCompleted event.
158
- //sessionImplementor.AfterTransactionCompletion(false, null);
159
- enlistment . Done ( ) ;
160
- IsInActiveTransaction = false ;
161
- }
162
- }
120
+ => ProcessSecondPhase ( enlistment , false ) ;
163
121
164
122
void IEnlistmentNotification . InDoubt ( Enlistment enlistment )
123
+ => ProcessSecondPhase ( enlistment , null ) ;
124
+
125
+ private void ProcessSecondPhase ( Enlistment enlistment , bool ? success )
165
126
{
166
127
using ( new SessionIdLoggingContext ( sessionImplementor . SessionId ) )
167
128
{
168
- sessionImplementor . AfterTransactionCompletion ( false , null ) ;
169
- logger . Debug ( "DTC transaction is in doubt" ) ;
170
- enlistment . Done ( ) ;
129
+ logger . Debug ( success . HasValue
130
+ ? success . Value ? "committing DTC transaction" : "rolled back DTC transaction"
131
+ : "DTC transaction is in doubt" ) ;
132
+ // we have not much to do here, since it is the actual
133
+ // DB connection that will commit/rollback the transaction
171
134
IsInActiveTransaction = false ;
135
+ // In doubt means the transaction may get carried on successfully, but maybe one hour later, the
136
+ // time for the failing durable ressource to come back online and tell. We won't wait for knowing,
137
+ // so better be pessimist.
138
+ var signalSuccess = success ?? false ;
139
+ // May fail by releasing the connection while the connection has its own second phase to do.
140
+ // Since we can release connection before completing an ambient transaction, maybe it will never
141
+ // fail, but here we are at the transaction completion stage, which is not documented for
142
+ // supporting this. See next comment as for why we cannot do that within
143
+ // TransactionCompletion event.
144
+ sessionImplementor . AfterTransactionCompletion ( signalSuccess , null ) ;
145
+
146
+ if ( sessionImplementor . TransactionContext . ShouldCloseSessionOnDistributedTransactionCompleted )
147
+ {
148
+ sessionImplementor . CloseSessionFromDistributedTransaction ( ) ;
149
+ }
150
+ sessionImplementor . TransactionContext = null ;
151
+
152
+ // Do not signal it is finished before having processed after-transaction actions, otherwise they
153
+ // may be executed concurrently to next scope, which causes a bunch of issues.
154
+ enlistment . Done ( ) ;
172
155
}
173
156
}
174
157
0 commit comments