Skip to content

Commit 35708dd

Browse files
committed
CDRIVER-636 use electionId to detect stale primaries
1 parent 7574f23 commit 35708dd

10 files changed

+643
-13
lines changed

src/mongoc/mongoc-server-description-private.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ struct _mongoc_server_description_t
6868

6969
bson_t tags;
7070
const char *current_primary;
71+
bson_oid_t election_id;
7172
};
7273

7374
void
@@ -78,6 +79,9 @@ bool
7879
mongoc_server_description_has_rs_member (mongoc_server_description_t *description,
7980
const char *address);
8081

82+
bool
83+
mongoc_server_description_has_election_id (mongoc_server_description_t *description);
84+
8185
void
8286
mongoc_server_description_cleanup (mongoc_server_description_t *sd);
8387

@@ -88,6 +92,9 @@ void
8892
mongoc_server_description_set_state (mongoc_server_description_t *description,
8993
mongoc_server_description_type_t type);
9094
void
95+
mongoc_server_description_set_election_id (mongoc_server_description_t *description,
96+
const bson_oid_t *election_id);
97+
void
9198
mongoc_server_description_update_rtt (mongoc_server_description_t *server,
9299
int64_t new_time);
93100

src/mongoc/mongoc-server-description.c

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
static uint8_t kMongocEmptyBson[] = { 5, 0, 0, 0, 0 };
3131

32+
static bson_oid_t kObjectIdZero = { 0 };
33+
3234
/* Destroy allocated resources within @description, but don't free it */
3335
void
3436
mongoc_server_description_cleanup (mongoc_server_description_t *sd)
@@ -45,7 +47,7 @@ mongoc_server_description_reset (mongoc_server_description_t *sd)
4547
{
4648
BSON_ASSERT(sd);
4749

48-
/* set other fields to default or empty states */
50+
/* set other fields to default or empty states. election_id is zeroed. */
4951
memset (&sd->set_name, 0, sizeof (*sd) - ((char*)&sd->set_name - (char*)sd));
5052
sd->set_name = NULL;
5153
sd->type = MONGOC_SERVER_UNKNOWN;
@@ -191,6 +193,25 @@ mongoc_server_description_has_rs_member(mongoc_server_description_t *server,
191193
return false;
192194
}
193195

196+
/*
197+
*--------------------------------------------------------------------------
198+
*
199+
* mongoc_server_description_has_election_id --
200+
*
201+
* Did this server's ismaster response have an "electionId" field?
202+
*
203+
* Returns:
204+
* True if the server description's electionId is set.
205+
*
206+
*--------------------------------------------------------------------------
207+
*/
208+
209+
bool
210+
mongoc_server_description_has_election_id (mongoc_server_description_t *description)
211+
{
212+
return 0 != bson_oid_compare (&description->election_id, &kObjectIdZero);
213+
}
214+
194215
/*
195216
*--------------------------------------------------------------------------
196217
*
@@ -234,13 +255,7 @@ mongoc_server_description_host (mongoc_server_description_t *description)
234255
*
235256
* mongoc_server_description_set_state --
236257
*
237-
* Change the state of this server.
238-
*
239-
* Returns:
240-
* true, false
241-
*
242-
* Side effects:
243-
* None
258+
* Set the server description's server type.
244259
*
245260
*--------------------------------------------------------------------------
246261
*/
@@ -252,6 +267,31 @@ mongoc_server_description_set_state (mongoc_server_description_t *description,
252267
}
253268

254269

270+
/*
271+
*--------------------------------------------------------------------------
272+
*
273+
* mongoc_server_description_set_election_id --
274+
*
275+
* Set the election_id of this server. Copies the given ObjectId or,
276+
* if it is NULL, zeroes description's election_id.
277+
*
278+
* Side effects:
279+
* None.
280+
*
281+
*--------------------------------------------------------------------------
282+
*/
283+
void
284+
mongoc_server_description_set_election_id (mongoc_server_description_t *description,
285+
const bson_oid_t *election_id)
286+
{
287+
if (election_id) {
288+
bson_oid_copy_unsafe (election_id, &description->election_id);
289+
} else {
290+
bson_oid_copy_unsafe (&kObjectIdZero, &description->election_id);
291+
}
292+
}
293+
294+
255295
/*
256296
*-------------------------------------------------------------------------
257297
*
@@ -260,11 +300,8 @@ mongoc_server_description_set_state (mongoc_server_description_t *description,
260300
* Calculate this server's rtt calculation using an exponentially-
261301
* weighted moving average formula.
262302
*
263-
* Returns:
264-
* None.
265-
*
266303
* Side effects:
267-
* Changes this server description's rtt.
304+
* None.
268305
*
269306
*-------------------------------------------------------------------------
270307
*/
@@ -351,6 +388,9 @@ mongoc_server_description_handle_ismaster (
351388
} else if (strcmp ("setName", bson_iter_key (&iter)) == 0) {
352389
if (! BSON_ITER_HOLDS_UTF8 (&iter)) goto failure;
353390
sd->set_name = bson_iter_utf8 (&iter, NULL);
391+
} else if (strcmp ("electionId", bson_iter_key (&iter)) == 0) {
392+
if (! BSON_ITER_HOLDS_OID (&iter)) goto failure;
393+
mongoc_server_description_set_election_id (sd, bson_iter_oid (&iter));
354394
} else if (strcmp ("secondary", bson_iter_key (&iter)) == 0) {
355395
if (! BSON_ITER_HOLDS_BOOL (&iter)) goto failure;
356396
is_secondary = bson_iter_bool (&iter);

src/mongoc/mongoc-topology-description-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ typedef struct _mongoc_topology_description_t
3838
mongoc_topology_description_type_t type;
3939
mongoc_set_t *servers;
4040
char *set_name;
41+
bson_oid_t max_election_id;
4142
bool compatible;
4243
char *compatibility_error;
4344
uint32_t max_server_id;

src/mongoc/mongoc-topology-description.c

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,48 @@ _mongoc_topology_description_has_primary (mongoc_topology_description_t *descrip
143143
return primary;
144144
}
145145

146+
/*
147+
*--------------------------------------------------------------------------
148+
*
149+
* _mongoc_topology_description_later_election --
150+
*
151+
* Check if we've seen a more recent election in the replica set
152+
* than this server has.
153+
*
154+
* Returns:
155+
* True if the topology description's max election id is greater
156+
* than the server description's election_id.
157+
*
158+
* Side effects:
159+
* None
160+
*
161+
*--------------------------------------------------------------------------
162+
*/
163+
static bool
164+
_mongoc_topology_description_later_election (mongoc_topology_description_t *td,
165+
mongoc_server_description_t *sd)
166+
{
167+
return bson_oid_compare (&td->max_election_id, &sd->election_id) > 0;
168+
}
169+
170+
/*
171+
*--------------------------------------------------------------------------
172+
*
173+
* _mongoc_topology_description_set_max_election_id --
174+
*
175+
* Remember that we've seen a new election id. Unconditionally sets
176+
* td->max_election_id to sd->election_id.
177+
*
178+
*--------------------------------------------------------------------------
179+
*/
180+
static void
181+
_mongoc_topology_description_set_max_election_id (
182+
mongoc_topology_description_t *td,
183+
mongoc_server_description_t *sd)
184+
{
185+
bson_oid_copy (&sd->election_id, &td->max_election_id);
186+
}
187+
146188
static bool
147189
_mongoc_topology_description_server_is_candidate (
148190
mongoc_server_description_type_t desc_type,
@@ -812,7 +854,8 @@ _mongoc_topology_description_invalidate_primaries_cb (void *item,
812854

813855
if (server->id != data->primary->id &&
814856
server->type == MONGOC_SERVER_RS_PRIMARY) {
815-
mongoc_server_description_set_state(server, MONGOC_SERVER_UNKNOWN);
857+
mongoc_server_description_set_state (server, MONGOC_SERVER_UNKNOWN);
858+
mongoc_server_description_set_election_id (server, NULL);
816859
}
817860
return true;
818861
}
@@ -945,6 +988,23 @@ _mongoc_topology_description_update_rs_from_primary (mongoc_topology_description
945988
}
946989
}
947990

991+
if (mongoc_server_description_has_election_id (server)) {
992+
/* Server Discovery And Monitoring Spec: "The client remembers the
993+
* greatest electionId reported by a primary, and distrusts primaries
994+
* with lesser electionIds. This prevents the client from oscillating
995+
* between the old and new primary during a split-brain period."
996+
*/
997+
if (_mongoc_topology_description_later_election (topology, server)) {
998+
/* stale primary */
999+
mongoc_topology_description_invalidate_server (topology, server->id);
1000+
_update_rs_type (topology);
1001+
return;
1002+
}
1003+
1004+
/* server's electionId >= topology's max electionId */
1005+
_mongoc_topology_description_set_max_election_id (topology, server);
1006+
}
1007+
9481008
/* 'Server' is the primary! Invalidate other primaries if found */
9491009
data.primary = server;
9501010
data.topology = topology;

tests/TestSuite.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ extern "C" {
112112
} while (0)
113113

114114

115+
#define ASSERT_CMPOID(a, b) \
116+
do { \
117+
if (bson_oid_compare ((a), (b))) { \
118+
char oid_a[25]; \
119+
char oid_b[25]; \
120+
bson_oid_to_string ((a), oid_a); \
121+
bson_oid_to_string ((b), oid_b); \
122+
fprintf(stderr, \
123+
"FAIL\n\nAssert Failure: " \
124+
"ObjectId(\"%s\") != ObjectId(\"%s\")\n", \
125+
oid_a, oid_b); \
126+
abort(); \
127+
} \
128+
} while (0)
129+
130+
115131
#define ASSERT_CONTAINS(a, b) \
116132
do { \
117133
if (NULL == strstr ((a), (b))) { \
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"description": "New primary with equal electionId",
3+
"phases": [
4+
{
5+
"outcome": {
6+
"servers": {
7+
"a:27017": {
8+
"electionId": null,
9+
"setName": null,
10+
"type": "Unknown"
11+
},
12+
"b:27017": {
13+
"electionId": {
14+
"$oid": "000000000000000000000001"
15+
},
16+
"setName": "rs",
17+
"type": "RSPrimary"
18+
}
19+
},
20+
"setName": "rs",
21+
"topologyType": "ReplicaSetWithPrimary"
22+
},
23+
"responses": [
24+
[
25+
"a:27017",
26+
{
27+
"electionId": {
28+
"$oid": "000000000000000000000001"
29+
},
30+
"hosts": [
31+
"a:27017",
32+
"b:27017"
33+
],
34+
"ismaster": true,
35+
"ok": 1,
36+
"setName": "rs"
37+
}
38+
],
39+
[
40+
"b:27017",
41+
{
42+
"electionId": {
43+
"$oid": "000000000000000000000001"
44+
},
45+
"hosts": [
46+
"a:27017",
47+
"b:27017"
48+
],
49+
"ismaster": true,
50+
"ok": 1,
51+
"setName": "rs"
52+
}
53+
]
54+
]
55+
}
56+
],
57+
"uri": "mongodb://a/?replicaSet=rs"
58+
}

0 commit comments

Comments
 (0)