Skip to content

Commit b87d016

Browse files
committed
CSHARP-2286: Invalidate Server description for write concern errors also.
1 parent b6803ec commit b87d016

File tree

2 files changed

+305
-3
lines changed

2 files changed

+305
-3
lines changed

src/MongoDB.Driver.Core/Core/Servers/Server.cs

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,13 @@
2424
using MongoDB.Bson;
2525
using MongoDB.Bson.IO;
2626
using MongoDB.Bson.Serialization;
27-
using MongoDB.Bson.Serialization.Serializers;
2827
using MongoDB.Driver.Core.Bindings;
2928
using MongoDB.Driver.Core.Clusters;
3029
using MongoDB.Driver.Core.Configuration;
3130
using MongoDB.Driver.Core.ConnectionPools;
3231
using MongoDB.Driver.Core.Connections;
3332
using MongoDB.Driver.Core.Events;
3433
using MongoDB.Driver.Core.Misc;
35-
using MongoDB.Driver.Core.Operations;
3634
using MongoDB.Driver.Core.WireProtocol;
3735
using MongoDB.Driver.Core.WireProtocol.Messages;
3836
using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;
@@ -256,7 +254,7 @@ private void HandleChannelException(IConnection connection, Exception ex)
256254
return;
257255
}
258256

259-
if (__invalidatingExceptions.Contains(ex.GetType()))
257+
if (ShouldInvalidateServer(ex))
260258
{
261259
Invalidate();
262260
}
@@ -266,6 +264,96 @@ private void HandleChannelException(IConnection connection, Exception ex)
266264
}
267265
}
268266

267+
private bool IsNotMaster(ServerErrorCode code, string message)
268+
{
269+
switch (code)
270+
{
271+
case ServerErrorCode.NotMaster: // 10107
272+
case ServerErrorCode.NotMasterNoSlaveOk: // 13435
273+
return true;
274+
}
275+
276+
if (message != null)
277+
{
278+
if (message.IndexOf("not master", StringComparison.OrdinalIgnoreCase) != -1 &&
279+
message.IndexOf("not master or secondary", StringComparison.OrdinalIgnoreCase) == -1)
280+
{
281+
return true;
282+
}
283+
}
284+
285+
return false;
286+
}
287+
288+
private bool IsNotMasterOrRecovering(ServerErrorCode code, string message)
289+
{
290+
return IsNotMaster(code, message) || IsRecovering(code, message);
291+
}
292+
293+
private bool IsRecovering(ServerErrorCode code, string message)
294+
{
295+
switch (code)
296+
{
297+
case ServerErrorCode.InterruptedAtShutdown: // 11600
298+
case ServerErrorCode.InterruptedDueToReplStateChange: // 11602
299+
case ServerErrorCode.NotMasterOrSecondary: // 13436
300+
case ServerErrorCode.PrimarySteppedDown: // 189
301+
case ServerErrorCode.ShutdownInProgress: // 91
302+
return true;
303+
}
304+
305+
if (message != null)
306+
{
307+
if (message.IndexOf("not master or secondary", StringComparison.OrdinalIgnoreCase) != -1 ||
308+
message.IndexOf("node is recovering", StringComparison.OrdinalIgnoreCase) != -1)
309+
{
310+
return true;
311+
}
312+
}
313+
314+
return false;
315+
}
316+
317+
private bool ShouldInvalidateServer(Exception exception)
318+
{
319+
if (__invalidatingExceptions.Contains(exception.GetType()))
320+
{
321+
return true;
322+
}
323+
324+
var commandException = exception as MongoCommandException;
325+
if (commandException != null)
326+
{
327+
var code = (ServerErrorCode)commandException.Code;
328+
var message = commandException.ErrorMessage;
329+
330+
if (IsNotMasterOrRecovering(code, message))
331+
{
332+
return true;
333+
}
334+
335+
if (commandException.GetType() == typeof(MongoWriteConcernException))
336+
{
337+
var writeConcernException = (MongoWriteConcernException)commandException;
338+
var writeConcernResult = writeConcernException.WriteConcernResult;
339+
var response = writeConcernResult.Response;
340+
var writeConcernError = response["writeConcernError"].AsBsonDocument;
341+
if (writeConcernError != null)
342+
{
343+
code = (ServerErrorCode)writeConcernError.GetValue("code", -1).ToInt32();
344+
message = writeConcernError.GetValue("errmsg", null)?.AsString;
345+
346+
if (IsNotMasterOrRecovering(code, message))
347+
{
348+
return true;
349+
}
350+
}
351+
}
352+
}
353+
354+
return false;
355+
}
356+
269357
private void ThrowIfDisposed()
270358
{
271359
if (_state.Value == State.Disposed)

tests/MongoDB.Driver.Core.Tests/Core/Servers/ServerTests.cs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
*/
1515

1616
using System;
17+
using System.IO;
1718
using System.Net;
19+
using System.Net.Sockets;
20+
using System.Reflection;
1821
using System.Threading;
1922
using System.Threading.Tasks;
2023
using FluentAssertions;
@@ -252,6 +255,190 @@ public void A_description_changed_event_with_a_heartbeat_exception_should_clear_
252255

253256
_mockConnectionPool.Verify(p => p.Clear(), Times.Once);
254257
}
258+
259+
[Theory]
260+
[InlineData((ServerErrorCode)(-1), false)]
261+
[InlineData(ServerErrorCode.NotMaster, true)]
262+
[InlineData(ServerErrorCode.NotMasterNoSlaveOk, true)]
263+
[InlineData(ServerErrorCode.NotMasterOrSecondary, false)]
264+
internal void IsNotMaster_should_return_expected_result_for_code(ServerErrorCode code, bool expectedResult)
265+
{
266+
_subject.Initialize();
267+
268+
var result = _subject.IsNotMaster(code, null);
269+
270+
result.Should().Be(expectedResult);
271+
if (result)
272+
{
273+
_subject.IsRecovering(code, null).Should().BeFalse();
274+
}
275+
}
276+
277+
[Theory]
278+
[InlineData(null, false)]
279+
[InlineData("abc", false)]
280+
[InlineData("not master", true)]
281+
[InlineData("not master or secondary", false)]
282+
internal void IsNotMaster_should_return_expected_result_for_message(string message, bool expectedResult)
283+
{
284+
_subject.Initialize();
285+
286+
var result = _subject.IsNotMaster((ServerErrorCode)(-1), message);
287+
288+
result.Should().Be(expectedResult);
289+
if (result)
290+
{
291+
_subject.IsRecovering((ServerErrorCode)(-1), message).Should().BeFalse();
292+
}
293+
}
294+
295+
[Theory]
296+
[InlineData((ServerErrorCode)(-1), false)]
297+
[InlineData(ServerErrorCode.NotMaster, true)]
298+
[InlineData(ServerErrorCode.InterruptedAtShutdown, true)]
299+
internal void IsNotMasterOrRecovering_should_return_expected_result(ServerErrorCode code, bool expectedResult)
300+
{
301+
_subject.Initialize();
302+
303+
var result = _subject.IsNotMasterOrRecovering(code, null);
304+
305+
result.Should().Be(expectedResult);
306+
}
307+
308+
[Theory]
309+
[InlineData((ServerErrorCode)(-1), false)]
310+
[InlineData(ServerErrorCode.InterruptedAtShutdown, true)]
311+
[InlineData(ServerErrorCode.InterruptedDueToReplStateChange, true)]
312+
[InlineData(ServerErrorCode.NotMasterOrSecondary, true)]
313+
[InlineData(ServerErrorCode.PrimarySteppedDown, true)]
314+
[InlineData(ServerErrorCode.ShutdownInProgress, true)]
315+
internal void IsRecovering_should_return_expected_result_for_code(ServerErrorCode code, bool expectedResult)
316+
{
317+
_subject.Initialize();
318+
319+
var result = _subject.IsRecovering(code, null);
320+
321+
result.Should().Be(expectedResult);
322+
if (result)
323+
{
324+
_subject.IsNotMaster(code, null).Should().BeFalse();
325+
}
326+
}
327+
328+
[Theory]
329+
[InlineData(null, false)]
330+
[InlineData("abc", false)]
331+
[InlineData("node is recovering", true)]
332+
[InlineData("not master or secondary", true)]
333+
internal void IsRecovering_should_return_expected_result_for_message(string message, bool expectedResult)
334+
{
335+
_subject.Initialize();
336+
337+
var result = _subject.IsRecovering((ServerErrorCode)(-1), message);
338+
339+
result.Should().Be(expectedResult);
340+
if (result)
341+
{
342+
_subject.IsNotMaster((ServerErrorCode)(-1), message).Should().BeFalse();
343+
}
344+
}
345+
346+
[Theory]
347+
[InlineData(nameof(EndOfStreamException), true)]
348+
[InlineData(nameof(Exception), false)]
349+
[InlineData(nameof(IOException), true)]
350+
[InlineData(nameof(MongoConnectionException), true)]
351+
[InlineData(nameof(MongoNodeIsRecoveringException), true)]
352+
[InlineData(nameof(MongoNotPrimaryException), true)]
353+
[InlineData(nameof(SocketException), true)]
354+
internal void ShouldInvalidateServer_should_return_expected_result_for_exceptionType(string exceptionTypeName, bool expectedResult)
355+
{
356+
_subject.Initialize();
357+
Exception exception;
358+
var clusterId = new ClusterId(1);
359+
var serverId = new ServerId(clusterId, new DnsEndPoint("localhost", 27017));
360+
var connectionId = new ConnectionId(serverId);
361+
var command = new BsonDocument("command", 1);
362+
var commandResult = new BsonDocument("ok", 1);
363+
switch (exceptionTypeName)
364+
{
365+
case nameof(EndOfStreamException): exception = new EndOfStreamException(); break;
366+
case nameof(Exception): exception = new Exception(); break;
367+
case nameof(IOException): exception = new IOException(); break;
368+
case nameof(MongoConnectionException): exception = new MongoConnectionException(connectionId, "message"); break;
369+
case nameof(MongoNodeIsRecoveringException): exception = new MongoNodeIsRecoveringException(connectionId, command, commandResult); break;
370+
case nameof(MongoNotPrimaryException): exception = new MongoNotPrimaryException(connectionId, command, commandResult); break;
371+
case nameof(SocketException): exception = new SocketException(); break;
372+
default: throw new Exception($"Invalid exceptionTypeName: {exceptionTypeName}.");
373+
}
374+
375+
var result = _subject.ShouldInvalidateServer(exception);
376+
377+
result.Should().Be(expectedResult);
378+
}
379+
380+
[Theory]
381+
[InlineData(null, null, false)]
382+
[InlineData((ServerErrorCode)(-1), null, false)]
383+
[InlineData(ServerErrorCode.NotMaster, null, true)]
384+
[InlineData(ServerErrorCode.InterruptedAtShutdown, null, true)]
385+
[InlineData(null, "abc", false)]
386+
[InlineData(null, "not master", true)]
387+
[InlineData(null, "not master or secondary", true)]
388+
[InlineData(null, "node is recovering", true)]
389+
internal void ShouldInvalidateServer_should_return_expected_result_for_MongoCommandException(ServerErrorCode? code, string message, bool expectedResult)
390+
{
391+
_subject.Initialize();
392+
var clusterId = new ClusterId(1);
393+
var serverId = new ServerId(clusterId, new DnsEndPoint("localhost", 27017));
394+
var connectionId = new ConnectionId(serverId);
395+
var command = new BsonDocument("command", 1);
396+
var commandResult = new BsonDocument
397+
{
398+
{ "ok", 0 },
399+
{ "code", () => (int)code.Value, code.HasValue },
400+
{ "errmsg", message, message != null }
401+
};
402+
var exception = new MongoCommandException(connectionId, "message", command, commandResult);
403+
404+
var result = _subject.ShouldInvalidateServer(exception);
405+
406+
result.Should().Be(expectedResult);
407+
}
408+
409+
[Theory]
410+
[InlineData(null, null, false)]
411+
[InlineData((ServerErrorCode)(-1), null, false)]
412+
[InlineData(ServerErrorCode.NotMaster, null, true)]
413+
[InlineData(ServerErrorCode.InterruptedAtShutdown, null, true)]
414+
[InlineData(null, "abc", false)]
415+
[InlineData(null, "not master", true)]
416+
[InlineData(null, "not master or secondary", true)]
417+
[InlineData(null, "node is recovering", true)]
418+
internal void ShouldInvalidateServer_should_return_expected_result_for_MongoWriteConcernException(ServerErrorCode? code, string message, bool expectedResult)
419+
{
420+
_subject.Initialize();
421+
var clusterId = new ClusterId(1);
422+
var serverId = new ServerId(clusterId, new DnsEndPoint("localhost", 27017));
423+
var connectionId = new ConnectionId(serverId);
424+
var command = new BsonDocument("command", 1);
425+
var commandResult = new BsonDocument
426+
{
427+
{ "ok", 1 },
428+
{ "writeConcernError", new BsonDocument
429+
{
430+
{ "code", () => (int)code.Value, code.HasValue },
431+
{ "errmsg", message, message != null }
432+
}
433+
}
434+
};
435+
var writeConcernResult = new WriteConcernResult(commandResult);
436+
var exception = new MongoWriteConcernException(connectionId, "message", writeConcernResult);
437+
438+
var result = _subject.ShouldInvalidateServer(exception);
439+
440+
result.Should().Be(expectedResult);
441+
}
255442
}
256443

257444
public class ServerChannelTests
@@ -346,4 +533,31 @@ public void Command_should_update_the_session_and_cluster_cluster_times()
346533
}
347534
}
348535
}
536+
537+
internal static class ServerReflector
538+
{
539+
public static bool IsNotMaster(this Server server, ServerErrorCode code, string message)
540+
{
541+
var methodInfo = typeof(Server).GetMethod(nameof(IsNotMaster), BindingFlags.NonPublic | BindingFlags.Instance);
542+
return (bool)methodInfo.Invoke(server, new object[] { code, message });
543+
}
544+
545+
public static bool IsNotMasterOrRecovering(this Server server, ServerErrorCode code, string message)
546+
{
547+
var methodInfo = typeof(Server).GetMethod(nameof(IsNotMasterOrRecovering), BindingFlags.NonPublic | BindingFlags.Instance);
548+
return (bool)methodInfo.Invoke(server, new object[] { code, message });
549+
}
550+
551+
public static bool IsRecovering(this Server server, ServerErrorCode code, string message)
552+
{
553+
var methodInfo = typeof(Server).GetMethod(nameof(IsRecovering), BindingFlags.NonPublic | BindingFlags.Instance);
554+
return (bool)methodInfo.Invoke(server, new object[] { code, message });
555+
}
556+
557+
public static bool ShouldInvalidateServer(this Server server, Exception exception)
558+
{
559+
var methodInfo = typeof(Server).GetMethod(nameof(ShouldInvalidateServer), BindingFlags.NonPublic | BindingFlags.Instance);
560+
return (bool)methodInfo.Invoke(server, new object[] { exception });
561+
}
562+
}
349563
}

0 commit comments

Comments
 (0)