Skip to content

Commit e6a5bfa

Browse files
CSHARP-2820: Add SDAM test for debouncing topology description updates.
1 parent 5caa4ee commit e6a5bfa

File tree

10 files changed

+236
-41
lines changed

10 files changed

+236
-41
lines changed

src/MongoDB.Driver.Core/Core/Clusters/Cluster.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ private void RapidHeartbeatTimerCallback(object args)
222222

223223
protected abstract void RequestHeartbeat();
224224

225-
protected void OnDescriptionChanged(ClusterDescription oldDescription, ClusterDescription newDescription)
225+
protected void OnDescriptionChanged(ClusterDescription oldDescription, ClusterDescription newDescription, bool shouldClusterDescriptionChangedEventBePublished)
226226
{
227-
if (_descriptionChangedEventHandler != null)
227+
if (shouldClusterDescriptionChangedEventBePublished && _descriptionChangedEventHandler != null)
228228
{
229229
_descriptionChangedEventHandler(new ClusterDescriptionChangedEvent(oldDescription, newDescription));
230230
}
@@ -305,7 +305,7 @@ public ICoreSessionHandle StartSession(CoreSessionOptions options)
305305

306306
protected abstract bool TryGetServer(EndPoint endPoint, out IClusterableServer server);
307307

308-
protected void UpdateClusterDescription(ClusterDescription newClusterDescription)
308+
protected void UpdateClusterDescription(ClusterDescription newClusterDescription, bool shouldClusterDescriptionChangedEventBePublished = true)
309309
{
310310
ClusterDescription oldClusterDescription = null;
311311
TaskCompletionSource<bool> oldDescriptionChangedTaskCompletionSource = null;
@@ -319,7 +319,7 @@ protected void UpdateClusterDescription(ClusterDescription newClusterDescription
319319
_descriptionChangedTaskCompletionSource = new TaskCompletionSource<bool>();
320320
}
321321

322-
OnDescriptionChanged(oldClusterDescription, newClusterDescription);
322+
OnDescriptionChanged(oldClusterDescription, newClusterDescription, shouldClusterDescriptionChangedEventBePublished);
323323

324324
// TODO: use RunContinuationsAsynchronously instead once we require a new enough .NET Framework
325325
Task.Run(() => oldDescriptionChangedTaskCompletionSource.TrySetResult(true));

src/MongoDB.Driver.Core/Core/Clusters/MultiServerCluster.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,8 @@ private void ProcessServerDescriptionChanged(ServerDescriptionChangedEventArgs a
323323
}
324324
}
325325

326-
UpdateClusterDescription(newClusterDescription);
326+
var shouldClusterDescriptionChangedEventBePublished = !args.OldServerDescription.SdamEquals(args.NewServerDescription);
327+
UpdateClusterDescription(newClusterDescription, shouldClusterDescriptionChangedEventBePublished);
327328
}
328329

329330
foreach (var server in newServers)

src/MongoDB.Driver.Core/Core/Clusters/SingleServerCluster.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ private void ServerDescriptionChanged(object sender, ServerDescriptionChangedEve
210210
}
211211
}
212212

213-
UpdateClusterDescription(newClusterDescription);
213+
var shouldClusterDescriptionChangedEventBePublished = !args.OldServerDescription.SdamEquals(args.NewServerDescription);
214+
UpdateClusterDescription(newClusterDescription, shouldClusterDescriptionChangedEventBePublished);
214215
}
215216

216217
protected override bool TryGetServer(EndPoint endPoint, out IClusterableServer server)

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.Globalization;
1918
using System.Linq;
2019
using System.Net;
2120
using System.Net.Sockets;
@@ -130,6 +129,14 @@ public static List<object> GetObjectData(EndPoint value)
130129
/// <returns>True if both sequences contain the same end points in the same order, or if both sequences are null.</returns>
131130
public static bool SequenceEquals(IEnumerable<EndPoint> a, IEnumerable<EndPoint> b)
132131
{
132+
if (a == null && b == null)
133+
{
134+
return true;
135+
}
136+
if (a == null || b == null)
137+
{
138+
return false;
139+
}
133140
return a.SequenceEqual(b, __endPointEqualityComparer);
134141
}
135142

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ private void OnDescriptionChanged(object sender, ServerDescriptionChangedEventAr
222222
_connectionPool.Clear();
223223
}
224224

225-
if (_descriptionChangedEventHandler != null)
225+
var shouldServerDescriptionChangedEventBePublished = !e.OldServerDescription.SdamEquals(e.NewServerDescription);
226+
if (shouldServerDescriptionChangedEventBePublished && _descriptionChangedEventHandler != null)
226227
{
227228
_descriptionChangedEventHandler(new ServerDescriptionChangedEvent(e.OldServerDescription, e.NewServerDescription));
228229
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,27 @@ public override int GetHashCode()
472472
.GetHashCode();
473473
}
474474

475+
/// <summary>
476+
/// Determines whether the specified <see cref="ServerDescription" /> can be considered as equal to decide should we publish sdam events or not.
477+
/// </summary>
478+
/// <param name="other">The other server description.</param>
479+
/// <returns><c>true</c>, if sdam events should be suppressed, otherwise <c>false</c>.</returns>
480+
public bool SdamEquals(ServerDescription other)
481+
{
482+
return
483+
EndPointHelper.Equals(_endPoint, other._endPoint) &&
484+
_type == other.Type &&
485+
object.Equals(_wireVersionRange, other._wireVersionRange) &&
486+
EndPointHelper.Equals(_canonicalEndPoint, other._canonicalEndPoint) && // me
487+
EndPointHelper.SequenceEquals(_replicaSetConfig?.Members, other._replicaSetConfig?.Members) && // hosts, passives, arbiters
488+
object.Equals(_tags, other._tags) &&
489+
_replicaSetConfig?.Name == other._replicaSetConfig?.Name && // setName
490+
_replicaSetConfig?.Version == other._replicaSetConfig?.Version && // setVersion
491+
object.Equals(_electionId, other._electionId) &&
492+
EndPointHelper.Equals(_replicaSetConfig?.Primary, other._replicaSetConfig?.Primary) && // primary
493+
_logicalSessionTimeout == other._logicalSessionTimeout;
494+
}
495+
475496
/// <inheritdoc/>
476497
public override string ToString()
477498
{

tests/MongoDB.Driver.Core.Tests/Core/Clusters/MultiServerClusterTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,6 @@ public void Should_not_remove_a_server_that_is_disconnected()
682682
description.Type.Should().Be(ClusterType.ReplicaSet);
683683
description.Servers.Should().BeEquivalentToWithComparer(GetDescriptions(_firstEndPoint, _secondEndPoint, _thirdEndPoint), _serverDescriptionComparer);
684684

685-
_capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
686685
_capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
687686
_capturedEvents.Any().Should().BeFalse();
688687
}

tests/MongoDB.Driver.Core.Tests/Specifications/server-discovery-and-monitoring/MonitoringTestRunner.cs

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,13 @@
1414
*/
1515

1616
using System;
17-
using System.Collections;
1817
using System.Collections.Generic;
19-
using System.IO;
2018
using System.Linq;
21-
using System.Reflection;
2219
using System.Threading;
2320
using FluentAssertions;
2421
using MongoDB.Bson;
2522
using MongoDB.Bson.TestHelpers;
23+
using MongoDB.Bson.TestHelpers.JsonDrivenTests;
2624
using MongoDB.Bson.TestHelpers.XunitExtensions;
2725
using MongoDB.Driver.Core;
2826
using MongoDB.Driver.Core.Clusters;
@@ -44,9 +42,11 @@ public class MonitoringTestRunner
4442

4543
[Theory]
4644
[ClassData(typeof(TestCaseFactory))]
47-
public void RunTestDefinition(BsonDocument definition)
45+
public void RunTestDefinition(JsonDrivenTestCase testCase)
4846
{
49-
VerifyFields(definition, "description", "path", "phases", "uri");
47+
var definition = testCase.Test;
48+
49+
VerifyFields(definition, "description", "_path", "phases", "uri");
5050

5151
_cluster = BuildCluster(definition);
5252
_cluster.Initialize();
@@ -348,36 +348,15 @@ private ICluster BuildCluster(BsonDocument definition)
348348
.CreateCluster();
349349
}
350350

351-
private class TestCaseFactory : IEnumerable<object[]>
351+
// nested types
352+
private class TestCaseFactory : JsonDrivenTestCaseFactory
352353
{
353-
public IEnumerator<object[]> GetEnumerator()
354-
{
355-
const string prefix = "MongoDB.Driver.Core.Tests.Specifications.server_discovery_and_monitoring.tests.monitoring.";
356-
var executingAssembly = typeof(TestCaseFactory).GetTypeInfo().Assembly;
357-
var enumerable = executingAssembly
358-
.GetManifestResourceNames()
359-
.Where(path => path.StartsWith(prefix) && path.EndsWith(".json"))
360-
.Select(path => ReadDefinition(path))
361-
.Select(definition => new object[] { definition });
362-
return enumerable.GetEnumerator();
363-
}
364-
365-
IEnumerator IEnumerable.GetEnumerator()
366-
{
367-
return GetEnumerator();
368-
}
354+
protected override string PathPrefix => "MongoDB.Driver.Core.Tests.Specifications.server_discovery_and_monitoring.tests.monitoring.";
369355

370-
private static BsonDocument ReadDefinition(string path)
356+
protected override IEnumerable<JsonDrivenTestCase> CreateTestCases(BsonDocument document)
371357
{
372-
var executingAssembly = typeof(TestCaseFactory).GetTypeInfo().Assembly;
373-
using (var definitionStream = executingAssembly.GetManifestResourceStream(path))
374-
using (var definitionStreamReader = new StreamReader(definitionStream))
375-
{
376-
var definitionString = definitionStreamReader.ReadToEnd();
377-
var definition = BsonDocument.Parse(definitionString);
378-
definition.InsertAt(0, new BsonElement("path", path));
379-
return definition;
380-
}
358+
var name = GetTestCaseName(document, document, 0);
359+
yield return new JsonDrivenTestCase(name, document, document);
381360
}
382361
}
383362
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
{
2+
"description": "Monitoring a standalone connection - suppress update events for equal server descriptions",
3+
"uri": "mongodb://a:27017",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"a:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": true,
12+
"minWireVersion": 0,
13+
"maxWireVersion": 4
14+
}
15+
],
16+
[
17+
"a:27017",
18+
{
19+
"ok": 1,
20+
"ismaster": true,
21+
"minWireVersion": 0,
22+
"maxWireVersion": 4
23+
}
24+
]
25+
],
26+
"outcome": {
27+
"events": [
28+
{
29+
"topology_opening_event": {
30+
"topologyId": "42"
31+
}
32+
},
33+
{
34+
"topology_description_changed_event": {
35+
"topologyId": "42",
36+
"previousDescription": {
37+
"topologyType": "Unknown",
38+
"servers": []
39+
},
40+
"newDescription": {
41+
"topologyType": "Single",
42+
"servers": [
43+
{
44+
"address": "a:27017",
45+
"arbiters": [],
46+
"hosts": [],
47+
"passives": [],
48+
"type": "Unknown"
49+
}
50+
]
51+
}
52+
}
53+
},
54+
{
55+
"server_opening_event": {
56+
"topologyId": "42",
57+
"address": "a:27017"
58+
}
59+
},
60+
{
61+
"server_description_changed_event": {
62+
"topologyId": "42",
63+
"address": "a:27017",
64+
"previousDescription": {
65+
"address": "a:27017",
66+
"arbiters": [],
67+
"hosts": [],
68+
"passives": [],
69+
"type": "Unknown"
70+
},
71+
"newDescription": {
72+
"address": "a:27017",
73+
"arbiters": [],
74+
"hosts": [],
75+
"passives": [],
76+
"type": "Standalone"
77+
}
78+
}
79+
},
80+
{
81+
"topology_description_changed_event": {
82+
"topologyId": "42",
83+
"previousDescription": {
84+
"topologyType": "Single",
85+
"servers": [
86+
{
87+
"address": "a:27017",
88+
"arbiters": [],
89+
"hosts": [],
90+
"passives": [],
91+
"type": "Unknown"
92+
}
93+
]
94+
},
95+
"newDescription": {
96+
"topologyType": "Single",
97+
"servers": [
98+
{
99+
"address": "a:27017",
100+
"arbiters": [],
101+
"hosts": [],
102+
"passives": [],
103+
"type": "Standalone"
104+
}
105+
]
106+
}
107+
}
108+
}
109+
]
110+
}
111+
}
112+
]
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
description: "Monitoring a standalone connection - suppress update events for equal server descriptions"
2+
uri: "mongodb://a:27017"
3+
phases:
4+
-
5+
responses:
6+
-
7+
- "a:27017"
8+
- { ok: 1, ismaster: true, minWireVersion: 0, maxWireVersion: 4 }
9+
-
10+
- "a:27017"
11+
- { ok: 1, ismaster: true, minWireVersion: 0, maxWireVersion: 4 }
12+
13+
outcome:
14+
events:
15+
-
16+
topology_opening_event:
17+
topologyId: "42"
18+
-
19+
topology_description_changed_event:
20+
topologyId: "42"
21+
previousDescription:
22+
topologyType: "Unknown"
23+
servers: []
24+
newDescription:
25+
topologyType: "Single"
26+
servers:
27+
-
28+
address: "a:27017"
29+
arbiters: []
30+
hosts: []
31+
passives: []
32+
type: "Unknown"
33+
-
34+
server_opening_event:
35+
topologyId: "42"
36+
address: "a:27017"
37+
-
38+
server_description_changed_event:
39+
topologyId: "42"
40+
address: "a:27017"
41+
previousDescription:
42+
address: "a:27017"
43+
arbiters: []
44+
hosts: []
45+
passives: []
46+
type: "Unknown"
47+
newDescription:
48+
address: "a:27017"
49+
arbiters: []
50+
hosts: []
51+
passives: []
52+
type: "Standalone"
53+
-
54+
topology_description_changed_event:
55+
topologyId: "42"
56+
previousDescription:
57+
topologyType: "Single"
58+
servers:
59+
-
60+
address: "a:27017"
61+
arbiters: []
62+
hosts: []
63+
passives: []
64+
type: "Unknown"
65+
newDescription:
66+
topologyType: "Single"
67+
servers:
68+
-
69+
address: "a:27017"
70+
arbiters: []
71+
hosts: []
72+
passives: []
73+
type: "Standalone"

0 commit comments

Comments
 (0)