Skip to content

Commit 11b39ef

Browse files
CSHARP-2872: Expand use of error labels for RetryableWrites.
1 parent 14955fe commit 11b39ef

File tree

55 files changed

+3448
-128
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3448
-128
lines changed

src/MongoDB.Driver.Core/Core/Misc/Feature.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ public class Feature
7474
private static readonly Feature __partialIndexes = new Feature("PartialIndexes", new SemanticVersion(3, 2, 0));
7575
private static readonly ReadConcernFeature __readConcern = new ReadConcernFeature("ReadConcern", new SemanticVersion(3, 2, 0));
7676
private static readonly Feature __retryableReads = new Feature("RetryableReads", new SemanticVersion(3, 6, 0));
77+
private static readonly Feature __retryableWrites = new Feature("RetryableWrites", new SemanticVersion(3, 6, 0));
7778
private static readonly Feature __scramSha1Authentication = new Feature("ScramSha1Authentication", new SemanticVersion(3, 0, 0));
7879
private static readonly Feature __scramSha256Authentication = new Feature("ScramSha256Authentication", new SemanticVersion(4, 0, 0, ""));
7980
private static readonly Feature __serverExtractsUsernameFromX509Certificate = new Feature("ServerExtractsUsernameFromX509Certificate", new SemanticVersion(3, 3, 12));
81+
private static readonly Feature __serverReturnsRetryableWriteErrorLabel = new Feature("ServerReturnsRetryableWriteErrorLabel", new SemanticVersion(4, 3, 0));
8082
private static readonly Feature __shardedTransactions = new Feature("ShardedTransactions", new SemanticVersion(4, 1, 6));
8183
private static readonly Feature __tailableCursor = new Feature("TailableCursor", new SemanticVersion(3, 2, 0));
8284
private static readonly Feature __transactions = new Feature("Transactions", new SemanticVersion(4, 0, 0));
@@ -340,6 +342,11 @@ public class Feature
340342
/// </summary>
341343
public static Feature RetryableReads => __retryableReads;
342344

345+
/// <summary>
346+
/// Gets the retryable writes feature.
347+
/// </summary>
348+
public static Feature RetryableWrites => __retryableWrites;
349+
343350
/// <summary>
344351
/// Gets the scram sha1 authentication feature.
345352
/// </summary>
@@ -355,6 +362,11 @@ public class Feature
355362
/// </summary>
356363
public static Feature ServerExtractsUsernameFromX509Certificate => __serverExtractsUsernameFromX509Certificate;
357364

365+
/// <summary>
366+
/// Gets the server returns retryable writeError label feature.
367+
/// </summary>
368+
public static Feature ServerReturnsRetryableWriteErrorLabel => __serverReturnsRetryableWriteErrorLabel;
369+
358370
/// <summary>
359371
/// Gets the sharded transactions feature.
360372
/// </summary>

src/MongoDB.Driver.Core/Core/Operations/RetryabilityHelper.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq;
19+
using MongoDB.Bson;
1920

2021
namespace MongoDB.Driver.Core.Operations
2122
{
2223
internal static class RetryabilityHelper
2324
{
25+
// private constants
26+
private const string RetryableWriteErrorLabel = "RetryableWriteError";
27+
2428
// private static fields
2529
private static readonly HashSet<ServerErrorCode> __notResumableChangeStreamErrorCodes;
2630
private static readonly HashSet<string> __notResumableChangeStreamErrorLabels;
@@ -45,7 +49,6 @@ static RetryabilityHelper()
4549
typeof(MongoCursorNotFoundException)
4650
};
4751

48-
4952
__retryableReadExceptions = new HashSet<Type>(resumableAndRetryableExceptions);
5053

5154
__retryableWriteExceptions = new HashSet<Type>(resumableAndRetryableExceptions);
@@ -62,7 +65,7 @@ static RetryabilityHelper()
6265

6366
__retryableWriteErrorCodes = new HashSet<ServerErrorCode>(resumableAndRetryableErrorCodes)
6467
{
65-
ServerErrorCode.WriteConcernFailed
68+
ServerErrorCode.ExceededTimeLimit
6669
};
6770

6871
__notResumableChangeStreamErrorCodes = new HashSet<ServerErrorCode>()
@@ -79,6 +82,14 @@ static RetryabilityHelper()
7982
}
8083

8184
// public static methods
85+
public static void AddRetryableWriteErrorLabelIfRequired(MongoException exception)
86+
{
87+
if (ShouldRetryableWriteExceptionLabelBeAdded(exception))
88+
{
89+
exception.AddErrorLabel(RetryableWriteErrorLabel);
90+
}
91+
}
92+
8293
public static bool IsResumableChangeStreamException(Exception exception)
8394
{
8495
var commandException = exception as MongoCommandException;
@@ -117,6 +128,12 @@ public static bool IsRetryableReadException(Exception exception)
117128
}
118129

119130
public static bool IsRetryableWriteException(Exception exception)
131+
{
132+
return exception is MongoException mongoException ? mongoException.HasErrorLabel(RetryableWriteErrorLabel) : false;
133+
}
134+
135+
// private static methods
136+
private static bool ShouldRetryableWriteExceptionLabelBeAdded(Exception exception)
120137
{
121138
if (__retryableWriteExceptions.Contains(exception.GetType()))
122139
{
@@ -153,15 +170,9 @@ public static bool IsRetryableWriteException(Exception exception)
153170
case ServerErrorCode.HostUnreachable:
154171
case ServerErrorCode.NetworkTimeout:
155172
case ServerErrorCode.SocketException:
173+
case ServerErrorCode.ExceededTimeLimit:
156174
return true;
157175
}
158-
159-
var message = writeConcernError.GetValue("errmsg", null)?.AsString;
160-
if (message.IndexOf("not master", StringComparison.OrdinalIgnoreCase) != -1 ||
161-
message.IndexOf("node is recovering", StringComparison.OrdinalIgnoreCase) != -1)
162-
{
163-
return true;
164-
}
165176
}
166177
}
167178

src/MongoDB.Driver.Core/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,8 @@ public TCommandResult Execute(IConnection connection, CancellationToken cancella
122122
}
123123
catch (Exception exception)
124124
{
125-
if (exception is MongoException mongoException && ShouldAddTransientTransactionError(mongoException))
126-
{
127-
mongoException.AddErrorLabel("TransientTransactionError");
128-
}
125+
AddErrorLabelIfRequired(exception, connection.Description.ServerVersion);
126+
129127
TransactionHelper.UnpinServerIfNeededOnCommandException(_session, exception);
130128
throw;
131129
}
@@ -164,16 +162,32 @@ public async Task<TCommandResult> ExecuteAsync(IConnection connection, Cancellat
164162
}
165163
catch (Exception exception)
166164
{
167-
if (exception is MongoException mongoException && ShouldAddTransientTransactionError(mongoException))
168-
{
169-
mongoException.AddErrorLabel("TransientTransactionError");
170-
}
165+
AddErrorLabelIfRequired(exception, connection.Description.ServerVersion);
166+
171167
TransactionHelper.UnpinServerIfNeededOnCommandException(_session, exception);
172168
throw;
173169
}
174170
}
175171

176172
// private methods
173+
private void AddErrorLabelIfRequired(Exception exception, SemanticVersion serverVersion)
174+
{
175+
if (exception is MongoException mongoException)
176+
{
177+
if (ShouldAddTransientTransactionError(mongoException))
178+
{
179+
mongoException.AddErrorLabel("TransientTransactionError");
180+
}
181+
182+
if ((exception is MongoConnectionException) || // network error
183+
(Feature.RetryableWrites.IsSupported(serverVersion) &&
184+
!Feature.ServerReturnsRetryableWriteErrorLabel.IsSupported(serverVersion)))
185+
{
186+
RetryabilityHelper.AddRetryableWriteErrorLabelIfRequired(mongoException);
187+
}
188+
}
189+
}
190+
177191
private CommandResponseMessage AutoDecryptFieldsIfNecessary(CommandResponseMessage encryptedResponseMessage, CancellationToken cancellationToken)
178192
{
179193
if (_documentFieldDecryptor == null)

src/MongoDB.Driver.Core/MongoCommandException.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ namespace MongoDB.Driver
3131
public class MongoCommandException : MongoServerException
3232
{
3333
#region static
34-
private static void AddErrorLabelsFromCommandResult(MongoCommandException exception, BsonDocument result)
34+
/// <summary>
35+
/// Adds error labels from a command result document into the top-level label list.
36+
/// </summary>
37+
/// <param name="exception">The exception.</param>
38+
/// <param name="result">The result document.</param>
39+
protected static void AddErrorLabelsFromCommandResult(MongoCommandException exception, BsonDocument result)
3540
{
3641
// note: make a best effort to extract the error labels from the result, but never throw an exception
3742
if (result != null)

src/MongoDB.Driver.Core/MongoWriteConcernException.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@
1313
* limitations under the License.
1414
*/
1515

16-
using System;
1716
#if NET452
17+
using System;
1818
using System.Runtime.Serialization;
1919
#endif
20-
using MongoDB.Bson;
21-
using MongoDB.Bson.Serialization;
2220
using MongoDB.Driver.Core.Connections;
2321
using MongoDB.Driver.Core.Misc;
24-
using MongoDB.Driver.Core.Operations;
2522

2623
namespace MongoDB.Driver
2724
{
@@ -33,6 +30,21 @@ namespace MongoDB.Driver
3330
#endif
3431
public class MongoWriteConcernException : MongoCommandException
3532
{
33+
#region static
34+
private static void AddErrorLabelsFromWriteConcernResult(MongoWriteConcernException exception, WriteConcernResult writeConcernResult)
35+
{
36+
// note: make a best effort to extract the error labels from the writeConcernResult, but never throw an exception
37+
if (writeConcernResult != null && writeConcernResult.Response != null)
38+
{
39+
if (writeConcernResult.Response.TryGetValue("writeConcernError", out var writeConcernError) &&
40+
writeConcernError.IsBsonDocument)
41+
{
42+
AddErrorLabelsFromCommandResult(exception, writeConcernError.AsBsonDocument);
43+
}
44+
}
45+
}
46+
#endregion
47+
3648
// fields
3749
private readonly WriteConcernResult _writeConcernResult;
3850

@@ -47,6 +59,8 @@ public MongoWriteConcernException(ConnectionId connectionId, string message, Wri
4759
: base(connectionId, message, null, writeConcernResult.Response)
4860
{
4961
_writeConcernResult = Ensure.IsNotNull(writeConcernResult, nameof(writeConcernResult));
62+
63+
AddErrorLabelsFromWriteConcernResult(this, _writeConcernResult);
5064
}
5165

5266
#if NET452
@@ -59,6 +73,8 @@ public MongoWriteConcernException(SerializationInfo info, StreamingContext conte
5973
: base(info, context)
6074
{
6175
_writeConcernResult = (WriteConcernResult)info.GetValue("_writeConcernResult", typeof(WriteConcernResult));
76+
77+
AddErrorLabelsFromWriteConcernResult(this, _writeConcernResult);
6278
}
6379
#endif
6480

src/MongoDB.Driver.Core/ServerErrorCode.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal enum ServerErrorCode
2222
CappedPositionLost = 136,
2323
CursorKilled = 237,
2424
ElectionInProgress = 216,
25-
ExceededTimeLimit = 50,
25+
ExceededTimeLimit = 262,
2626
HostNotFound = 7,
2727
HostUnreachable = 6,
2828
Interrupted = 11601,
@@ -38,6 +38,7 @@ internal enum ServerErrorCode
3838
SocketException = 9001,
3939
UnknownReplWriteConcern = 79,
4040
UnsatisfiableWriteConcern = 100,
41-
WriteConcernFailed = 64
41+
WriteConcernFailed = 64,
42+
MaxTimeMSExpired = 50
4243
}
4344
}

tests/MongoDB.Driver.Core.TestHelpers/CoreExceptionHelper.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,22 @@ public static MongoCommandException CreateMongoCommandException(int code = 1, st
9292

9393
return commandException;
9494
}
95+
96+
public static MongoCommandException CreateMongoWriteConcernException(BsonDocument writeConcernResultDocument, string label = null)
97+
{
98+
var clusterId = new ClusterId(1);
99+
var endPoint = new DnsEndPoint("localhost", 27017);
100+
var serverId = new ServerId(clusterId, endPoint);
101+
var connectionId = new ConnectionId(serverId);
102+
var message = "Fake MongoWriteConcernException";
103+
var writeConcernResult = new WriteConcernResult(writeConcernResultDocument);
104+
var writeConcernException = new MongoWriteConcernException(connectionId, message, writeConcernResult);
105+
if (label != null)
106+
{
107+
writeConcernException.AddErrorLabel(label);
108+
}
109+
110+
return writeConcernException;
111+
}
95112
}
96113
}

0 commit comments

Comments
 (0)