Skip to content

Commit c59e798

Browse files
committed
JAVA-2069: Use both setVersion and electionId to detect a stale primary
1 parent ac638b2 commit c59e798

File tree

11 files changed

+178
-40
lines changed

11 files changed

+178
-40
lines changed

driver-core/src/main/com/mongodb/connection/DescriptionHelper.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2015 MongoDB, Inc.
2+
* Copyright 2008-2016 MongoDB, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,6 +81,7 @@ static ServerDescription createServerDescription(final ServerAddress serverAddre
8181
.maxWireVersion(isMasterResult.getInt32("maxWireVersion",
8282
new BsonInt32(getDefaultMaxWireVersion())).getValue())
8383
.electionId(getElectionId(isMasterResult))
84+
.setVersion(getSetVersion(isMasterResult))
8485
.roundTripTime(roundTripTime, NANOSECONDS)
8586
.ok(CommandHelper.isCommandOk(isMasterResult)).build();
8687
}
@@ -89,6 +90,10 @@ private static ObjectId getElectionId(final BsonDocument isMasterResult) {
8990
return isMasterResult.containsKey("electionId") ? isMasterResult.getObjectId("electionId").getValue() : null;
9091
}
9192

93+
private static Integer getSetVersion(final BsonDocument isMasterResult) {
94+
return isMasterResult.containsKey("setVersion") ? isMasterResult.getNumber("setVersion").intValue() : null;
95+
}
96+
9297
private static int getMaxMessageSizeBytes(final BsonDocument isMasterResult) {
9398
return isMasterResult.getInt32("maxMessageSizeBytes", new BsonInt32(getDefaultMaxMessageSize())).getValue();
9499
}

driver-core/src/main/com/mongodb/connection/MultiServerCluster.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2008-2014 MongoDB, Inc.
2+
* Copyright 2008-2016 MongoDB, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ final class MultiServerCluster extends BaseCluster {
4747
private ClusterType clusterType;
4848
private String replicaSetName;
4949
private ObjectId maxElectionId;
50+
private Integer maxSetVersion;
5051

5152
private final ConcurrentMap<ServerAddress, ServerTuple> addressToServerTupleMap =
5253
new ConcurrentHashMap<ServerAddress, ServerTuple>();
@@ -216,21 +217,35 @@ private boolean handleReplicaSetMemberChanged(final ServerDescription newDescrip
216217
}
217218

218219
if (newDescription.isPrimary()) {
219-
if (newDescription.getElectionId() != null) {
220-
if (maxElectionId != null && maxElectionId.compareTo(newDescription.getElectionId()) > 0) {
220+
if (newDescription.getSetVersion() != null && newDescription.getElectionId() != null) {
221+
if (isStalePrimary(newDescription)) {
221222
if (LOGGER.isInfoEnabled()) {
222-
LOGGER.info(format("Invalidating potential primary %s whose election id %s is less than the max election id seen "
223-
+ "so far %s", newDescription.getAddress(), newDescription.getElectionId(), maxElectionId));
223+
LOGGER.info(format("Invalidating potential primary %s whose (set version, election id) tuple of (%d, %s) "
224+
+ "is less than one already seen of (%d, %s)",
225+
newDescription.getAddress(),
226+
newDescription.getSetVersion(), newDescription.getElectionId(),
227+
maxSetVersion, maxElectionId));
224228
}
225229
addressToServerTupleMap.get(newDescription.getAddress()).server.invalidate();
226230
return false;
227231
}
228232

233+
if (!newDescription.getElectionId().equals(maxElectionId)) {
234+
if (LOGGER.isInfoEnabled()) {
235+
LOGGER.info(format("Setting max election id to %s from replica set primary %s", newDescription.getElectionId(),
236+
newDescription.getAddress()));
237+
}
238+
maxElectionId = newDescription.getElectionId();
239+
}
240+
}
241+
242+
if (newDescription.getSetVersion() != null
243+
&& (maxSetVersion == null || newDescription.getSetVersion().compareTo(maxSetVersion) > 0)) {
229244
if (LOGGER.isInfoEnabled()) {
230-
LOGGER.info(format("Setting max election id to %s from replica set primary %s", newDescription.getElectionId(),
231-
newDescription.getAddress()));
245+
LOGGER.info(format("Setting max set version to %d from replica set primary %s", newDescription.getSetVersion(),
246+
newDescription.getAddress()));
232247
}
233-
maxElectionId = newDescription.getElectionId();
248+
maxSetVersion = newDescription.getSetVersion();
234249
}
235250

236251
if (isNotAlreadyPrimary(newDescription.getAddress())) {
@@ -241,6 +256,15 @@ private boolean handleReplicaSetMemberChanged(final ServerDescription newDescrip
241256
return true;
242257
}
243258

259+
private boolean isStalePrimary(final ServerDescription newDescription) {
260+
if (maxSetVersion == null || maxElectionId == null) {
261+
return false;
262+
}
263+
264+
return (maxSetVersion.compareTo(newDescription.getSetVersion()) > 0
265+
|| (maxSetVersion.equals(newDescription.getSetVersion()) && maxElectionId.compareTo(newDescription.getElectionId()) > 0));
266+
}
267+
244268
private boolean isNotAlreadyPrimary(final ServerAddress address) {
245269
ServerTuple serverTuple = addressToServerTupleMap.get(address);
246270
return serverTuple == null || !serverTuple.description.isPrimary();

driver-core/src/main/com/mongodb/connection/ServerDescription.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2008-2014 MongoDB, Inc.
2+
* Copyright 2008-2016 MongoDB, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -69,6 +69,7 @@ public class ServerDescription {
6969
private final int maxWireVersion;
7070

7171
private final ObjectId electionId;
72+
private final Integer setVersion;
7273

7374
private final Throwable exception;
7475

@@ -113,6 +114,7 @@ public static class Builder {
113114
private int minWireVersion = 0;
114115
private int maxWireVersion = 0;
115116
private ObjectId electionId;
117+
private Integer setVersion;
116118
private Throwable exception;
117119

118120
/**
@@ -311,6 +313,17 @@ public Builder electionId(final ObjectId electionId) {
311313
return this;
312314
}
313315

316+
/**
317+
* Sets the setVersion reported by this server.
318+
*
319+
* @param setVersion the set version
320+
* @return this
321+
*/
322+
public Builder setVersion(final Integer setVersion) {
323+
this.setVersion = setVersion;
324+
return this;
325+
}
326+
314327
/**
315328
* Sets the exception thrown while attempting to determine the server description.
316329
*
@@ -519,6 +532,15 @@ public ObjectId getElectionId() {
519532
return electionId;
520533
}
521534

535+
/**
536+
* The replica set setVersion reported by this MongoDB server.
537+
*
538+
* @return the setVersion, which may be null
539+
*/
540+
public Integer getSetVersion() {
541+
return setVersion;
542+
}
543+
522544
/**
523545
* Returns true if the server has the given tags. A server of either type {@code ServerType.STANDALONE} or {@code
524546
* ServerType.SHARD_ROUTER} is considered to have all tags, so this method will always return true for instances of either of those
@@ -677,6 +699,9 @@ public boolean equals(final Object o) {
677699
if (electionId != null ? !electionId.equals(that.electionId) : that.electionId != null) {
678700
return false;
679701
}
702+
if (setVersion != null ? !setVersion.equals(that.setVersion) : that.setVersion != null) {
703+
return false;
704+
}
680705

681706
// Compare class equality and message as exceptions rarely override equals
682707
Class<?> thisExceptionClass = exception != null ? exception.getClass() : null;
@@ -707,6 +732,7 @@ public int hashCode() {
707732
result = 31 * result + tagSet.hashCode();
708733
result = 31 * result + (setName != null ? setName.hashCode() : 0);
709734
result = 31 * result + (electionId != null ? electionId.hashCode() : 0);
735+
result = 31 * result + (setVersion != null ? setVersion.hashCode() : 0);
710736
result = 31 * result + (ok ? 1 : 0);
711737
result = 31 * result + state.hashCode();
712738
result = 31 * result + version.hashCode();
@@ -729,7 +755,6 @@ public String toString() {
729755
+ ", version=" + version
730756
+ ", minWireVersion=" + minWireVersion
731757
+ ", maxWireVersion=" + maxWireVersion
732-
+ ", electionId=" + electionId
733758
+ ", maxDocumentSize=" + maxDocumentSize
734759
+ ", roundTripTimeNanos=" + roundTripTimeNanos
735760
: "")
@@ -742,7 +767,9 @@ public String toString() {
742767
+ ", arbiters=" + arbiters
743768
+ ", primary='" + primary + '\''
744769
+ ", tagSet=" + tagSet
745-
: "")
770+
+ ", electionId=" + electionId
771+
+ ", setVersion=" + setVersion
772+
: "")
746773
+ (exception == null ? "" : ", exception=" + translateExceptionToString())
747774
+ '}';
748775
}
@@ -803,6 +830,7 @@ private String getRoundTripFormattedInMilliseconds() {
803830
minWireVersion = builder.minWireVersion;
804831
maxWireVersion = builder.maxWireVersion;
805832
electionId = builder.electionId;
833+
setVersion = builder.setVersion;
806834
exception = builder.exception;
807835
}
808836
}

driver-core/src/test/resources/server-discovery-and-monitoring/rs/equal_electionids.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
"a:27017": {
88
"electionId": null,
99
"setName": null,
10+
"setVersion": null,
1011
"type": "Unknown"
1112
},
1213
"b:27017": {
1314
"electionId": {
1415
"$oid": "000000000000000000000001"
1516
},
1617
"setName": "rs",
18+
"setVersion": 1,
1719
"type": "RSPrimary"
1820
}
1921
},
@@ -33,7 +35,8 @@
3335
],
3436
"ismaster": true,
3537
"ok": 1,
36-
"setName": "rs"
38+
"setName": "rs",
39+
"setVersion": 1
3740
}
3841
],
3942
[
@@ -48,7 +51,8 @@
4851
],
4952
"ismaster": true,
5053
"ok": 1,
51-
"setName": "rs"
54+
"setName": "rs",
55+
"setVersion": 1
5256
}
5357
]
5458
]

driver-core/src/test/resources/server-discovery-and-monitoring/rs/new_primary_new_electionid.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"description": "New primary with greater electionId",
2+
"description": "New primary with greater setVersion and electionId",
33
"phases": [
44
{
55
"outcome": {
@@ -9,6 +9,7 @@
99
"$oid": "000000000000000000000001"
1010
},
1111
"setName": "rs",
12+
"setVersion": 1,
1213
"type": "RSPrimary"
1314
},
1415
"b:27017": {
@@ -33,7 +34,8 @@
3334
],
3435
"ismaster": true,
3536
"ok": 1,
36-
"setName": "rs"
37+
"setName": "rs",
38+
"setVersion": 1
3739
}
3840
]
3941
]
@@ -51,6 +53,7 @@
5153
"$oid": "000000000000000000000002"
5254
},
5355
"setName": "rs",
56+
"setVersion": 1,
5457
"type": "RSPrimary"
5558
}
5659
},
@@ -70,7 +73,8 @@
7073
],
7174
"ismaster": true,
7275
"ok": 1,
73-
"setName": "rs"
76+
"setName": "rs",
77+
"setVersion": 1
7478
}
7579
]
7680
]
@@ -88,6 +92,7 @@
8892
"$oid": "000000000000000000000002"
8993
},
9094
"setName": "rs",
95+
"setVersion": 1,
9196
"type": "RSPrimary"
9297
}
9398
},
@@ -107,7 +112,8 @@
107112
],
108113
"ismaster": true,
109114
"ok": 1,
110-
"setName": "rs"
115+
"setName": "rs",
116+
"setVersion": 1
111117
}
112118
]
113119
]

driver-core/src/test/resources/server-discovery-and-monitoring/rs/null_election_id.json

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"a:27017": {
88
"electionId": null,
99
"setName": "rs",
10+
"setVersion": 1,
1011
"type": "RSPrimary"
1112
},
1213
"b:27017": {
@@ -34,7 +35,8 @@
3435
],
3536
"ismaster": true,
3637
"ok": 1,
37-
"setName": "rs"
38+
"setName": "rs",
39+
"setVersion": 1
3840
}
3941
]
4042
]
@@ -52,6 +54,7 @@
5254
"$oid": "000000000000000000000002"
5355
},
5456
"setName": "rs",
57+
"setVersion": 1,
5558
"type": "RSPrimary"
5659
},
5760
"c:27017": {
@@ -77,7 +80,8 @@
7780
],
7881
"ismaster": true,
7982
"ok": 1,
80-
"setName": "rs"
83+
"setName": "rs",
84+
"setVersion": 1
8185
}
8286
]
8387
]
@@ -88,6 +92,7 @@
8892
"a:27017": {
8993
"electionId": null,
9094
"setName": "rs",
95+
"setVersion": 1,
9196
"type": "RSPrimary"
9297
},
9398
"b:27017": {
@@ -115,7 +120,8 @@
115120
],
116121
"ismaster": true,
117122
"ok": 1,
118-
"setName": "rs"
123+
"setName": "rs",
124+
"setVersion": 1
119125
}
120126
]
121127
]
@@ -125,7 +131,8 @@
125131
"servers": {
126132
"a:27017": {
127133
"electionId": null,
128-
"setName": "rs",
134+
"setName": "rs",
135+
"setVersion": 1,
129136
"type": "RSPrimary"
130137
},
131138
"b:27017": {
@@ -156,7 +163,8 @@
156163
],
157164
"ismaster": true,
158165
"ok": 1,
159-
"setName": "rs"
166+
"setName": "rs",
167+
"setVersion": 1
160168
}
161169
]
162170
]

0 commit comments

Comments
 (0)