@@ -34,6 +34,7 @@ public sealed class CoreSession : ICoreSession
34
34
private readonly IClusterClock _clusterClock = new ClusterClock ( ) ;
35
35
private CoreTransaction _currentTransaction ;
36
36
private bool _disposed ;
37
+ private bool _isCommitTransactionInProgress ;
37
38
private readonly IOperationClock _operationClock = new OperationClock ( ) ;
38
39
private readonly CoreSessionOptions _options ;
39
40
private readonly ICoreServerSession _serverSession ;
@@ -75,7 +76,28 @@ public CoreSession(
75
76
public bool IsImplicit => _options . IsImplicit ;
76
77
77
78
/// <inheritdoc />
78
- public bool IsInTransaction => _currentTransaction != null ;
79
+ public bool IsInTransaction
80
+ {
81
+ get
82
+ {
83
+ if ( _currentTransaction != null )
84
+ {
85
+ switch ( _currentTransaction . State )
86
+ {
87
+ case CoreTransactionState . Aborted :
88
+ return false ;
89
+
90
+ case CoreTransactionState . Committed :
91
+ return _isCommitTransactionInProgress ; // when retrying a commit we are temporarily "back in" the already committed transaction
92
+
93
+ default :
94
+ return true ;
95
+ }
96
+ }
97
+
98
+ return false ;
99
+ }
100
+ }
79
101
80
102
/// <inheritdoc />
81
103
public BsonTimestamp OperationTime => _operationClock . OperationTime ;
@@ -90,11 +112,11 @@ public CoreSession(
90
112
/// <inheritdoc />
91
113
public void AbortTransaction ( CancellationToken cancellationToken = default ( CancellationToken ) )
92
114
{
93
- EnsureIsInTransaction ( nameof ( AbortTransaction ) ) ;
115
+ EnsureAbortTransactionCanBeCalled ( nameof ( AbortTransaction ) ) ;
94
116
95
117
try
96
118
{
97
- if ( _currentTransaction . StatementId == 0 )
119
+ if ( _currentTransaction . IsEmpty )
98
120
{
99
121
return ;
100
122
}
@@ -130,18 +152,18 @@ public CoreSession(
130
152
}
131
153
finally
132
154
{
133
- _currentTransaction = null ;
155
+ _currentTransaction . SetState ( CoreTransactionState . Aborted ) ;
134
156
}
135
157
}
136
158
137
159
/// <inheritdoc />
138
160
public async Task AbortTransactionAsync ( CancellationToken cancellationToken = default ( CancellationToken ) )
139
161
{
140
- EnsureIsInTransaction ( nameof ( AbortTransaction ) ) ;
162
+ EnsureAbortTransactionCanBeCalled ( nameof ( AbortTransaction ) ) ;
141
163
142
164
try
143
165
{
144
- if ( _currentTransaction . StatementId == 0 )
166
+ if ( _currentTransaction . IsEmpty )
145
167
{
146
168
return ;
147
169
}
@@ -177,7 +199,36 @@ public CoreSession(
177
199
}
178
200
finally
179
201
{
180
- _currentTransaction = null ;
202
+ _currentTransaction . SetState ( CoreTransactionState . Aborted ) ;
203
+ }
204
+ }
205
+
206
+ /// <inheritdoc />
207
+ public void AboutToSendCommand ( )
208
+ {
209
+ if ( _currentTransaction != null )
210
+ {
211
+ switch ( _currentTransaction . State )
212
+ {
213
+ case CoreTransactionState . Starting : // Starting changes to InProgress after the message is sent to the server
214
+ case CoreTransactionState . InProgress :
215
+ return ;
216
+
217
+ case CoreTransactionState . Aborted :
218
+ _currentTransaction = null ;
219
+ break ;
220
+
221
+ case CoreTransactionState . Committed :
222
+ // don't set to null when retrying a commit
223
+ if ( ! _isCommitTransactionInProgress )
224
+ {
225
+ _currentTransaction = null ;
226
+ }
227
+ return ;
228
+
229
+ default :
230
+ throw new Exception ( $ "Unexpected transaction state: { _currentTransaction . State } .") ;
231
+ }
181
232
}
182
233
}
183
234
@@ -202,11 +253,12 @@ public long AdvanceTransactionNumber()
202
253
/// <inheritdoc />
203
254
public void CommitTransaction ( CancellationToken cancellationToken = default ( CancellationToken ) )
204
255
{
205
- EnsureIsInTransaction ( nameof ( CommitTransaction ) ) ;
256
+ EnsureCommitTransactionCanBeCalled ( nameof ( CommitTransaction ) ) ;
206
257
207
258
try
208
259
{
209
- if ( _currentTransaction . StatementId == 0 )
260
+ _isCommitTransactionInProgress = true ;
261
+ if ( _currentTransaction . IsEmpty )
210
262
{
211
263
return ;
212
264
}
@@ -227,18 +279,20 @@ public long AdvanceTransactionNumber()
227
279
}
228
280
finally
229
281
{
230
- _currentTransaction = null ;
282
+ _isCommitTransactionInProgress = false ;
283
+ _currentTransaction . SetState ( CoreTransactionState . Committed ) ;
231
284
}
232
285
}
233
286
234
287
/// <inheritdoc />
235
288
public async Task CommitTransactionAsync ( CancellationToken cancellationToken = default ( CancellationToken ) )
236
289
{
237
- EnsureIsInTransaction ( nameof ( CommitTransaction ) ) ;
290
+ EnsureCommitTransactionCanBeCalled ( nameof ( CommitTransaction ) ) ;
238
291
239
292
try
240
293
{
241
- if ( _currentTransaction . StatementId == 0 )
294
+ _isCommitTransactionInProgress = true ;
295
+ if ( _currentTransaction . IsEmpty )
242
296
{
243
297
return ;
244
298
}
@@ -259,7 +313,8 @@ public long AdvanceTransactionNumber()
259
313
}
260
314
finally
261
315
{
262
- _currentTransaction = null ;
316
+ _isCommitTransactionInProgress = false ;
317
+ _currentTransaction . SetState ( CoreTransactionState . Committed ) ;
263
318
}
264
319
}
265
320
@@ -288,10 +343,7 @@ public void Dispose()
288
343
/// <inheritdoc />
289
344
public void StartTransaction ( TransactionOptions transactionOptions = null )
290
345
{
291
- if ( _currentTransaction != null )
292
- {
293
- throw new InvalidOperationException ( "Transaction already in progress." ) ;
294
- }
346
+ EnsureStartTransactionCanBeCalled ( ) ;
295
347
296
348
var transactionNumber = AdvanceTransactionNumber ( ) ;
297
349
var effectiveTransactionOptions = GetEffectiveTransactionOptions ( transactionOptions ) ;
@@ -317,11 +369,67 @@ private IReadOperation<BsonDocument> CreateCommitTransactionOperation()
317
369
return new CommitTransactionOperation ( GetTransactionWriteConcern ( ) ) ;
318
370
}
319
371
320
- private void EnsureIsInTransaction ( string methodName )
372
+ private void EnsureAbortTransactionCanBeCalled ( string methodName )
373
+ {
374
+ if ( _currentTransaction == null )
375
+ {
376
+ throw new InvalidOperationException ( $ "{ methodName } cannot be called when no transaction started.") ;
377
+ }
378
+
379
+ switch ( _currentTransaction . State )
380
+ {
381
+ case CoreTransactionState . Starting :
382
+ case CoreTransactionState . InProgress :
383
+ return ;
384
+
385
+ case CoreTransactionState . Aborted :
386
+ throw new InvalidOperationException ( $ "Cannot call { methodName } twice.") ;
387
+
388
+ case CoreTransactionState . Committed :
389
+ throw new InvalidOperationException ( $ "Cannot call { methodName } after calling CommitTransaction.") ;
390
+
391
+ default :
392
+ throw new Exception ( $ "{ methodName } called in unexpected transaction state: { _currentTransaction . State } .") ;
393
+ }
394
+ }
395
+
396
+ private void EnsureCommitTransactionCanBeCalled ( string methodName )
321
397
{
322
398
if ( _currentTransaction == null )
323
399
{
324
- throw new InvalidOperationException ( "No transaction started." ) ;
400
+ throw new InvalidOperationException ( $ "{ methodName } cannot be called when no transaction started.") ;
401
+ }
402
+
403
+ switch ( _currentTransaction . State )
404
+ {
405
+ case CoreTransactionState . Starting :
406
+ case CoreTransactionState . InProgress :
407
+ case CoreTransactionState . Committed :
408
+ return ;
409
+
410
+ case CoreTransactionState . Aborted :
411
+ throw new InvalidOperationException ( $ "Cannot call { methodName } after calling AbortTransaction.") ;
412
+
413
+ default :
414
+ throw new Exception ( $ "{ methodName } called in unexpected transaction state: { _currentTransaction . State } .") ;
415
+ }
416
+ }
417
+
418
+ private void EnsureStartTransactionCanBeCalled ( )
419
+ {
420
+ if ( _currentTransaction == null )
421
+ {
422
+ return ;
423
+ }
424
+
425
+ switch ( _currentTransaction . State )
426
+ {
427
+ case CoreTransactionState . Aborted :
428
+ case CoreTransactionState . Committed :
429
+ return ;
430
+
431
+ default :
432
+ throw new InvalidOperationException ( "Transaction already in progress." ) ;
325
433
}
326
434
}
327
435
0 commit comments