Skip to content

Commit b42eaf5

Browse files
committed
CSHARP-2221: Implement new session transaction states.
1 parent f4bf404 commit b42eaf5

File tree

29 files changed

+2865
-176
lines changed

29 files changed

+2865
-176
lines changed

src/MongoDB.Driver.Core/Core/Bindings/CoreSession.cs

Lines changed: 127 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public sealed class CoreSession : ICoreSession
3434
private readonly IClusterClock _clusterClock = new ClusterClock();
3535
private CoreTransaction _currentTransaction;
3636
private bool _disposed;
37+
private bool _isCommitTransactionInProgress;
3738
private readonly IOperationClock _operationClock = new OperationClock();
3839
private readonly CoreSessionOptions _options;
3940
private readonly ICoreServerSession _serverSession;
@@ -75,7 +76,28 @@ public CoreSession(
7576
public bool IsImplicit => _options.IsImplicit;
7677

7778
/// <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+
}
79101

80102
/// <inheritdoc />
81103
public BsonTimestamp OperationTime => _operationClock.OperationTime;
@@ -90,11 +112,11 @@ public CoreSession(
90112
/// <inheritdoc />
91113
public void AbortTransaction(CancellationToken cancellationToken = default(CancellationToken))
92114
{
93-
EnsureIsInTransaction(nameof(AbortTransaction));
115+
EnsureAbortTransactionCanBeCalled(nameof(AbortTransaction));
94116

95117
try
96118
{
97-
if (_currentTransaction.StatementId == 0)
119+
if (_currentTransaction.IsEmpty)
98120
{
99121
return;
100122
}
@@ -130,18 +152,18 @@ public CoreSession(
130152
}
131153
finally
132154
{
133-
_currentTransaction = null;
155+
_currentTransaction.SetState(CoreTransactionState.Aborted);
134156
}
135157
}
136158

137159
/// <inheritdoc />
138160
public async Task AbortTransactionAsync(CancellationToken cancellationToken = default(CancellationToken))
139161
{
140-
EnsureIsInTransaction(nameof(AbortTransaction));
162+
EnsureAbortTransactionCanBeCalled(nameof(AbortTransaction));
141163

142164
try
143165
{
144-
if (_currentTransaction.StatementId == 0)
166+
if (_currentTransaction.IsEmpty)
145167
{
146168
return;
147169
}
@@ -177,7 +199,36 @@ public CoreSession(
177199
}
178200
finally
179201
{
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+
}
181232
}
182233
}
183234

@@ -202,11 +253,12 @@ public long AdvanceTransactionNumber()
202253
/// <inheritdoc />
203254
public void CommitTransaction(CancellationToken cancellationToken = default(CancellationToken))
204255
{
205-
EnsureIsInTransaction(nameof(CommitTransaction));
256+
EnsureCommitTransactionCanBeCalled(nameof(CommitTransaction));
206257

207258
try
208259
{
209-
if (_currentTransaction.StatementId == 0)
260+
_isCommitTransactionInProgress = true;
261+
if (_currentTransaction.IsEmpty)
210262
{
211263
return;
212264
}
@@ -227,18 +279,20 @@ public long AdvanceTransactionNumber()
227279
}
228280
finally
229281
{
230-
_currentTransaction = null;
282+
_isCommitTransactionInProgress = false;
283+
_currentTransaction.SetState(CoreTransactionState.Committed);
231284
}
232285
}
233286

234287
/// <inheritdoc />
235288
public async Task CommitTransactionAsync(CancellationToken cancellationToken = default(CancellationToken))
236289
{
237-
EnsureIsInTransaction(nameof(CommitTransaction));
290+
EnsureCommitTransactionCanBeCalled(nameof(CommitTransaction));
238291

239292
try
240293
{
241-
if (_currentTransaction.StatementId == 0)
294+
_isCommitTransactionInProgress = true;
295+
if (_currentTransaction.IsEmpty)
242296
{
243297
return;
244298
}
@@ -259,7 +313,8 @@ public long AdvanceTransactionNumber()
259313
}
260314
finally
261315
{
262-
_currentTransaction = null;
316+
_isCommitTransactionInProgress = false;
317+
_currentTransaction.SetState(CoreTransactionState.Committed);
263318
}
264319
}
265320

@@ -288,10 +343,7 @@ public void Dispose()
288343
/// <inheritdoc />
289344
public void StartTransaction(TransactionOptions transactionOptions = null)
290345
{
291-
if (_currentTransaction != null)
292-
{
293-
throw new InvalidOperationException("Transaction already in progress.");
294-
}
346+
EnsureStartTransactionCanBeCalled();
295347

296348
var transactionNumber = AdvanceTransactionNumber();
297349
var effectiveTransactionOptions = GetEffectiveTransactionOptions(transactionOptions);
@@ -317,11 +369,67 @@ private IReadOperation<BsonDocument> CreateCommitTransactionOperation()
317369
return new CommitTransactionOperation(GetTransactionWriteConcern());
318370
}
319371

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)
321397
{
322398
if (_currentTransaction == null)
323399
{
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.");
325433
}
326434
}
327435

src/MongoDB.Driver.Core/Core/Bindings/CoreTransaction.cs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ namespace MongoDB.Driver.Core.Bindings
2121
public class CoreTransaction
2222
{
2323
// private fields
24-
private int _statementId;
24+
private bool _isEmpty;
25+
private CoreTransactionState _state;
2526
private readonly long _transactionNumber;
2627
private readonly TransactionOptions _transactionOptions;
2728

@@ -35,17 +36,26 @@ public CoreTransaction(long transactionNumber, TransactionOptions transactionOpt
3536
{
3637
_transactionNumber = transactionNumber;
3738
_transactionOptions = transactionOptions;
38-
_statementId = 0;
39+
_state = CoreTransactionState.Starting;
40+
_isEmpty = true;
3941
}
4042

4143
// public properties
4244
/// <summary>
43-
/// Gets the statement identifier.
45+
/// Gets a value indicating whether the transaction is empty.
4446
/// </summary>
4547
/// <value>
46-
/// The statement identifier.
48+
/// <c>true</c> if the transaction is empty; otherwise, <c>false</c>.
4749
/// </value>
48-
public int StatementId => _statementId;
50+
public bool IsEmpty => _isEmpty;
51+
52+
/// <summary>
53+
/// Gets the transaction state.
54+
/// </summary>
55+
/// <value>
56+
/// The transaction state.
57+
/// </value>
58+
public CoreTransactionState State => _state;
4959

5060
/// <summary>
5161
/// Gets the transaction number.
@@ -63,14 +73,14 @@ public CoreTransaction(long transactionNumber, TransactionOptions transactionOpt
6373
/// </value>
6474
public TransactionOptions TransactionOptions => _transactionOptions;
6575

66-
// public methods
67-
/// <summary>
68-
/// Advances the statement identifier.
69-
/// </summary>
70-
/// <param name="numberOfStatements">The number of statements to advance by.</param>
71-
public void AdvanceStatementId(int numberOfStatements)
76+
// internal methods
77+
internal void SetState(CoreTransactionState state)
7278
{
73-
_statementId += numberOfStatements;
79+
_state = state;
80+
if (state == CoreTransactionState.InProgress)
81+
{
82+
_isEmpty = false;
83+
}
7484
}
7585
}
7686
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* Copyright 2018-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
namespace MongoDB.Driver.Core.Bindings
17+
{
18+
/// <summary>
19+
/// Represents the current state of a Core transaction.
20+
/// </summary>
21+
public enum CoreTransactionState
22+
{
23+
/// <summary>
24+
/// StartTransaction has been called but no operations have been performed yet.
25+
/// </summary>
26+
Starting = 1,
27+
28+
/// <summary>
29+
/// The transaction is in progress.
30+
/// </summary>
31+
InProgress,
32+
33+
/// <summary>
34+
/// CommitTransaction has been called.
35+
/// </summary>
36+
Committed,
37+
38+
/// <summary>
39+
/// AbortTransaction has been called.
40+
/// </summary>
41+
Aborted
42+
}
43+
}

src/MongoDB.Driver.Core/Core/Bindings/ICoreSession.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ public interface ICoreSession : IDisposable
113113
/// <returns>A Task.</returns>
114114
Task AbortTransactionAsync(CancellationToken cancellationToken = default(CancellationToken));
115115

116+
/// <summary>
117+
/// The driver is about to send a command on this session. Called to track session state.
118+
/// </summary>
119+
void AboutToSendCommand();
120+
116121
/// <summary>
117122
/// Advances the cluster time.
118123
/// </summary>

src/MongoDB.Driver.Core/Core/Bindings/NoCoreSession.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ public static ICoreSessionHandle NewHandle()
9292
throw new NotSupportedException("NoCoreSession does not support AbortTransactionAsync.");
9393
}
9494

95+
/// <inheritdoc />
96+
public void AboutToSendCommand()
97+
{
98+
}
99+
95100
/// <inheritdoc />
96101
public void AdvanceClusterTime(BsonDocument newClusterTime)
97102
{

src/MongoDB.Driver.Core/Core/Bindings/WrappingCoreSession.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ public ICoreSession Wrapped
166166
return _wrapped.AbortTransactionAsync(cancellationToken);
167167
}
168168

169+
/// <inheritdoc />
170+
public virtual void AboutToSendCommand()
171+
{
172+
ThrowIfDisposed();
173+
_wrapped.AboutToSendCommand();
174+
}
175+
169176
/// <inheritdoc />
170177
public virtual void AdvanceClusterTime(BsonDocument newClusterTime)
171178
{

0 commit comments

Comments
 (0)